Compare commits

..

6 Commits

144 changed files with 12304 additions and 14357 deletions
-1288
View File
File diff suppressed because it is too large Load Diff
+4 -9
View File
@@ -18,10 +18,8 @@ jobs:
uses: gradle/actions/setup-gradle@v4 uses: gradle/actions/setup-gradle@v4
with: with:
gradle-version: '9.4.1' gradle-version: '9.4.1'
cache-read-only: true
cache-cleanup: on-success
- name: Build - name: Build
run: gradle build -x test --no-daemon run: gradle build --no-daemon
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Get Version Number from Gradle - name: Get Version Number from Gradle
@@ -33,13 +31,10 @@ jobs:
id: changelog id: changelog
run: | run: |
CHANGELOG_CONTENT=$(awk 'BEGIN {print_section=0;} /^## \[/ {if (print_section == 0) {print_section=1;} else {exit;}} print_section {print;}' CHANGELOG.md) CHANGELOG_CONTENT=$(awk 'BEGIN {print_section=0;} /^## \[/ {if (print_section == 0) {print_section=1;} else {exit;}} print_section {print;}' CHANGELOG.md)
CHANGELOG_ESCAPED=$(echo "$CHANGELOG_CONTENT" | sed ':a;N;$!ba;s/\n/%0A/g')
echo "Extracted latest release notes from CHANGELOG.md:" echo "Extracted latest release notes from CHANGELOG.md:"
echo "$CHANGELOG_CONTENT" echo -e "$CHANGELOG_CONTENT"
{ echo "content=$CHANGELOG_ESCAPED" >> "$GITHUB_OUTPUT"
echo 'content<<EOF'
echo "$CHANGELOG_CONTENT"
echo 'EOF'
} >> "$GITHUB_OUTPUT"
- name: Create Release - name: Create Release
uses: actions/create-release@v1 uses: actions/create-release@v1
id: create_release id: create_release
-3
View File
@@ -23,9 +23,6 @@ jobs:
uses: gradle/actions/setup-gradle@v4 uses: gradle/actions/setup-gradle@v4
with: with:
gradle-version: '9.4.1' gradle-version: '9.4.1'
cache-overwrite-existing: 'true'
cache-read-only: ${{ github.event_name == 'pull_request' }}
cache-cleanup: on-success
- name: Build - name: Build
run: gradle build -x test --no-daemon run: gradle build -x test --no-daemon
env: env:
+28 -12
View File
@@ -2,6 +2,32 @@ on:
push: push:
jobs: jobs:
build:
runs-on: ubuntu-latest
env:
JAVA_TOOL_OPTIONS: -Djavax.net.ssl.trustStoreType=JKS -Djavax.net.ssl.trustStore=/etc/ssl/certs/java/cacerts -Djavax.net.ssl.trustStorePassword=changeit
steps:
- uses: actions/checkout@v5
- name: Set up JDK 21
uses: actions/setup-java@v5
with:
java-version: '21'
distribution: 'zulu'
- name: Set up Gradle
uses: gradle/actions/setup-gradle@v4
with:
gradle-version: '9.4.1'
- name: Build
run: gradle build -x test --no-daemon
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Upload AntiVPN
uses: actions/upload-artifact@v4
with:
name: AntiVPN-Universal
path: build/libs/AntiVPN-*-universal.jar
build-and-test: build-and-test:
runs-on: ubuntu-latest runs-on: ubuntu-latest
env: env:
@@ -18,17 +44,7 @@ jobs:
uses: gradle/actions/setup-gradle@v4 uses: gradle/actions/setup-gradle@v4
with: with:
gradle-version: '9.4.1' gradle-version: '9.4.1'
cache-overwrite-existing: 'true' - name: Build and Test
cache-read-only: ${{ github.event_name == 'pull_request' }} run: gradle build --no-daemon
cache-cleanup: on-success
- name: Build
run: gradle build -x test --no-daemon
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Upload AntiVPN
uses: actions/upload-artifact@v4
with:
name: AntiVPN-Universal
path: build/libs/AntiVPN-*-universal.jar
- name: Test
run: gradle test --no-daemon --parallel
-1
View File
@@ -296,4 +296,3 @@ $RECYCLE.BIN/
# End of https://www.toptal.com/developers/gitignore/api/windows,macos,linux,maven,java,intellij,eclipse,netbeans # End of https://www.toptal.com/developers/gitignore/api/windows,macos,linux,maven,java,intellij,eclipse,netbeans
/.gradle/ /.gradle/
.grade/** .grade/**
/run/
-10
View File
@@ -10,12 +10,6 @@ dependencies {
implementation project(':Common:loader-utils') implementation project(':Common:loader-utils')
} }
tasks.processResources {
filesMatching('plugin.yml') {
expand(project: project, projectVersion: project.version)
}
}
shadowJar { shadowJar {
archiveClassifier.set('') archiveClassifier.set('')
@@ -30,8 +24,4 @@ tasks.named('shadowJar') {
dependsOn(':Bukkit:Plugin:shadowJar') dependsOn(':Bukkit:Plugin:shadowJar')
} }
tasks.named('compileJava') {
dependsOn(':Bukkit:Plugin:shadowJar')
}
tasks.build.dependsOn shadowJar tasks.build.dependsOn shadowJar
@@ -22,30 +22,31 @@ import org.bukkit.plugin.java.JavaPlugin;
public class BukkitLoaderPlugin extends JavaPlugin { public class BukkitLoaderPlugin extends JavaPlugin {
private static final String JAR_NAME = "antivpn-bukkit.jarinjar"; private static final String JAR_NAME = "antivpn-bukkit.jarinjar";
private static final String SOURCE_NAME = "antivpn-source.jarinjar"; private static final String SOURCE_NAME = "antivpn-source.jarinjar";
private static final String BOOTSTRAP_CLASS = "dev.brighten.antivpn.bukkit.BukkitPlugin"; private static final String BOOTSTRAP_CLASS = "dev.brighten.antivpn.bukkit.BukkitPlugin";
private final LoaderBootstrap plugin; private final LoaderBootstrap plugin;
public BukkitLoaderPlugin() { public BukkitLoaderPlugin() {
JarInJarClassLoader loader = JarInJarClassLoader loader = new JarInJarClassLoader(getClass().getClassLoader(), JAR_NAME, SOURCE_NAME);
new JarInJarClassLoader(getClass().getClassLoader(), JAR_NAME, SOURCE_NAME); this.plugin = loader.instantiatePlugin(BOOTSTRAP_CLASS, JavaPlugin.class, this);
this.plugin = loader.instantiatePlugin(BOOTSTRAP_CLASS, JavaPlugin.class, this); }
}
@Override @Override
public void onLoad() { public void onLoad() {
this.plugin.onLoad(getDataFolder()); this.plugin.onLoad(getDataFolder());
} }
@Override
public void onEnable() {
this.plugin.onEnable();
}
@Override
public void onDisable() {
this.plugin.onDisable();
}
@Override
public void onEnable() {
this.plugin.onEnable();
}
@Override
public void onDisable() {
this.plugin.onDisable();
}
} }
-1
View File
@@ -12,7 +12,6 @@ dependencies {
testImplementation 'org.mockito:mockito-core:5.11.0' testImplementation 'org.mockito:mockito-core:5.11.0'
testImplementation 'org.mockito:mockito-subclass:5.11.0' testImplementation 'org.mockito:mockito-subclass:5.11.0'
testImplementation 'org.mockito:mockito-junit-jupiter:5.11.0' testImplementation 'org.mockito:mockito-junit-jupiter:5.11.0'
testImplementation testFixtures(project(':Common:Source'))
} }
shadowJar { shadowJar {
@@ -19,37 +19,38 @@ package dev.brighten.antivpn.bukkit;
import dev.brighten.antivpn.AntiVPN; import dev.brighten.antivpn.AntiVPN;
import dev.brighten.antivpn.api.APIPlayer; import dev.brighten.antivpn.api.APIPlayer;
import dev.brighten.antivpn.command.CommandExecutor; import dev.brighten.antivpn.command.CommandExecutor;
import java.util.Optional;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.bukkit.ChatColor; import org.bukkit.ChatColor;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import java.util.Optional;
@RequiredArgsConstructor @RequiredArgsConstructor
public class BukkitCommandExecutor implements CommandExecutor { public class BukkitCommandExecutor implements CommandExecutor {
private final CommandSender sender; private final CommandSender sender;
@Override @Override
public void sendMessage(String message, Object... objects) { public void sendMessage(String message, Object... objects) {
sender.sendMessage( sender.sendMessage(ChatColor.translateAlternateColorCodes('&',
ChatColor.translateAlternateColorCodes('&', String.format(message, objects))); String.format(message, objects)));
} }
@Override @Override
public boolean hasPermission(String permission) { public boolean hasPermission(String permission) {
return sender.hasPermission(permission); return sender.hasPermission(permission);
} }
@Override @Override
public Optional<APIPlayer> getPlayer() { public Optional<APIPlayer> getPlayer() {
if (!isPlayer()) return Optional.empty(); if(!isPlayer()) return Optional.empty();
return AntiVPN.getInstance().getPlayerExecutor().getPlayer(((Player) sender).getUniqueId()); return AntiVPN.getInstance().getPlayerExecutor().getPlayer(((Player)sender).getUniqueId());
} }
@Override @Override
public boolean isPlayer() { public boolean isPlayer() {
return sender instanceof Player; return sender instanceof Player;
} }
} }
@@ -18,11 +18,9 @@ package dev.brighten.antivpn.bukkit;
import dev.brighten.antivpn.AntiVPN; import dev.brighten.antivpn.AntiVPN;
import dev.brighten.antivpn.api.APIPlayer; import dev.brighten.antivpn.api.APIPlayer;
import dev.brighten.antivpn.api.CheckResult;
import dev.brighten.antivpn.api.OfflinePlayer; import dev.brighten.antivpn.api.OfflinePlayer;
import dev.brighten.antivpn.api.VPNExecutor; import dev.brighten.antivpn.api.VPNExecutor;
import dev.brighten.antivpn.utils.StringUtil; import dev.brighten.antivpn.utils.StringUtil;
import java.util.logging.Level;
import net.md_5.bungee.api.ChatColor; import net.md_5.bungee.api.ChatColor;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.event.EventHandler; import org.bukkit.event.EventHandler;
@@ -34,90 +32,89 @@ import org.bukkit.event.player.PlayerLoginEvent;
import org.bukkit.event.player.PlayerQuitEvent; import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.scheduler.BukkitRunnable; import org.bukkit.scheduler.BukkitRunnable;
import java.util.logging.Level;
public class BukkitListener extends VPNExecutor implements Listener { public class BukkitListener extends VPNExecutor implements Listener {
@Override @Override
public void registerListeners() { public void registerListeners() {
Bukkit.getPluginManager().registerEvents(this, BukkitPlugin.pluginInstance.getPlugin()); Bukkit.getPluginManager()
} .registerEvents(this, BukkitPlugin.pluginInstance.getPlugin());
@Override
public void log(Level level, String log, Object... objects) {
Bukkit.getLogger().log(level, String.format(log, objects));
}
@Override
public void log(String log, Object... objects) {
log(Level.INFO, String.format(log, objects));
}
@Override
public void logException(String message, Throwable ex) {
Bukkit.getLogger().log(Level.SEVERE, message, ex);
}
@Override
public void runCommand(String command) {
new BukkitRunnable() {
public void run() {
Bukkit.getServer()
.dispatchCommand(
Bukkit.getConsoleSender(), ChatColor.translateAlternateColorCodes('&', command));
}
}.runTask(BukkitPlugin.pluginInstance.getPlugin());
}
@Override
public void disablePlugin() {
HandlerList.unregisterAll(this);
Bukkit.getPluginManager().disablePlugin(BukkitPlugin.pluginInstance.getPlugin());
}
@EventHandler(priority = EventPriority.HIGH)
public void onLogin(final PlayerLoginEvent event) {
APIPlayer player =
AntiVPN.getInstance()
.getPlayerExecutor()
.getPlayer(event.getPlayer().getUniqueId())
.orElse(
new OfflinePlayer(
event.getPlayer().getUniqueId(),
event.getPlayer().getName(),
event.getAddress()));
CheckResult result = player.checkPlayer();
if (!result.resultType().isShouldBlock()) return;
if (!AntiVPN.getInstance().getVpnConfig().isKickPlayers()) {
return;
} }
event.setResult(PlayerLoginEvent.Result.KICK_BANNED); @Override
event.setKickMessage( public void log(Level level, String log, Object... objects) {
switch (result.resultType()) { Bukkit.getLogger().log(level, String.format(log, objects));
case DENIED_COUNTRY -> }
StringUtil.varReplace(
AntiVPN.getInstance().getVpnConfig().getCountryVanillaKickReason(), @Override
player, public void log(String log, Object... objects) {
result.response()); log(Level.INFO, String.format(log, objects));
case DENIED_PROXY -> }
StringUtil.varReplace(
AntiVPN.getInstance().getVpnConfig().getKickMessage(), player, result.response()); @Override
default -> "You were kicked by KauriVPN for an unknown reason!"; public void logException(String message, Throwable ex) {
Bukkit.getLogger().log(Level.SEVERE, message, ex);
}
@Override
public void runCommand(String command) {
new BukkitRunnable() {
public void run() {
Bukkit.getServer().dispatchCommand(Bukkit.getConsoleSender(),
ChatColor.translateAlternateColorCodes('&', command));
}
}.runTask(BukkitPlugin.pluginInstance.getPlugin());
}
@Override
public void disablePlugin() {
HandlerList.unregisterAll(this);
Bukkit.getPluginManager().disablePlugin(BukkitPlugin.pluginInstance.getPlugin());
}
@EventHandler(priority = EventPriority.HIGH)
public void onLogin(final PlayerLoginEvent event) {
APIPlayer player = AntiVPN.getInstance().getPlayerExecutor().getPlayer(event.getPlayer().getUniqueId())
.orElse(new OfflinePlayer(
event.getPlayer().getUniqueId(),
event.getPlayer().getName(),
event.getAddress()
));
player.checkPlayer(result -> {
if(!result.resultType().isShouldBlock()) return;
if(!AntiVPN.getInstance().getVpnConfig().isKickPlayers()) {
return;
}
event.setResult(PlayerLoginEvent.Result.KICK_BANNED);
event.setKickMessage(switch (result.resultType()) {
case DENIED_COUNTRY -> StringUtil.varReplace(
AntiVPN.getInstance().getVpnConfig().getCountryVanillaKickReason(),
player,
result.response()
);
case DENIED_PROXY ->
StringUtil.varReplace(
AntiVPN.getInstance().getVpnConfig().getKickMessage(),
player,
result.response()
);
default -> "You were kicked by KauriVPN for an unknown reason!";
});
}); });
} }
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onJoin(final PlayerJoinEvent event) { public void onJoin(final PlayerJoinEvent event) {
AntiVPN.getInstance() AntiVPN.getInstance().getPlayerExecutor().getPlayer(event.getPlayer().getUniqueId())
.getPlayerExecutor() .ifPresent(APIPlayer::checkAlertsState);
.getPlayer(event.getPlayer().getUniqueId()) }
.ifPresent(APIPlayer::checkAlertsState);
}
@EventHandler @EventHandler
public void onQuit(PlayerQuitEvent event) { public void onQuit(PlayerQuitEvent event) {
AntiVPN.getInstance().getPlayerExecutor().unloadPlayer(event.getPlayer().getUniqueId()); AntiVPN.getInstance().getPlayerExecutor().unloadPlayer(event.getPlayer().getUniqueId());
} }
} }
@@ -23,33 +23,29 @@ import org.bukkit.scheduler.BukkitRunnable;
public class BukkitPlayer extends APIPlayer { public class BukkitPlayer extends APIPlayer {
private final Player player; private final Player player;
public BukkitPlayer(Player player) {
super(player.getUniqueId(), player.getName(), player.getAddress() != null ? player.getAddress().getAddress() : null);
public BukkitPlayer(Player player) { this.player = player;
super( }
player.getUniqueId(),
player.getName(),
player.getAddress() != null ? player.getAddress().getAddress() : null);
this.player = player; @Override
} public void sendMessage(String message) {
player.sendMessage(ChatColor.translateAlternateColorCodes('&', message));
}
@Override @Override
public void sendMessage(String message) { public void kickPlayer(String reason) {
player.sendMessage(ChatColor.translateAlternateColorCodes('&', message)); new BukkitRunnable() {
} public void run() {
player.kickPlayer(ChatColor.translateAlternateColorCodes('&', reason));
}
}.runTask(BukkitPlugin.pluginInstance.getPlugin());
}
@Override @Override
public void kickPlayer(String reason) { public boolean hasPermission(String permission) {
new BukkitRunnable() { return player.hasPermission(permission);
public void run() { }
player.kickPlayer(ChatColor.translateAlternateColorCodes('&', reason));
}
}.runTask(BukkitPlugin.pluginInstance.getPlugin());
}
@Override
public boolean hasPermission(String permission) {
return player.hasPermission(permission);
}
} }
@@ -18,48 +18,49 @@ package dev.brighten.antivpn.bukkit;
import dev.brighten.antivpn.api.APIPlayer; import dev.brighten.antivpn.api.APIPlayer;
import dev.brighten.antivpn.api.PlayerExecutor; import dev.brighten.antivpn.api.PlayerExecutor;
import java.util.*;
import java.util.stream.Collectors;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import java.util.*;
import java.util.stream.Collectors;
public class BukkitPlayerExecutor implements PlayerExecutor { public class BukkitPlayerExecutor implements PlayerExecutor {
private final Map<UUID, BukkitPlayer> cachedPlayers = new HashMap<>(); private final Map<UUID, BukkitPlayer> cachedPlayers = new HashMap<>();
@Override @Override
public Optional<APIPlayer> getPlayer(String name) { public Optional<APIPlayer> getPlayer(String name) {
final Player player = Bukkit.getPlayer(name); final Player player = Bukkit.getPlayer(name);
if (player == null) { if(player == null) {
return Optional.empty(); return Optional.empty();
}
return Optional.of(cachedPlayers.computeIfAbsent(player.getUniqueId(), k -> new BukkitPlayer(player)));
} }
return Optional.of( @Override
cachedPlayers.computeIfAbsent(player.getUniqueId(), k -> new BukkitPlayer(player))); public Optional<APIPlayer> getPlayer(UUID uuid) {
} final Player player = Bukkit.getPlayer(uuid);
@Override if(player == null) {
public Optional<APIPlayer> getPlayer(UUID uuid) { return Optional.empty();
final Player player = Bukkit.getPlayer(uuid); }
if (player == null) { return Optional.of(cachedPlayers.computeIfAbsent(player.getUniqueId(), k -> new BukkitPlayer(player)));
return Optional.empty();
} }
return Optional.of( @Override
cachedPlayers.computeIfAbsent(player.getUniqueId(), k -> new BukkitPlayer(player))); public void unloadPlayer(UUID uuid) {
} cachedPlayers.remove(uuid);
}
@Override
public void unloadPlayer(UUID uuid) {
cachedPlayers.remove(uuid);
}
@Override @Override
public List<APIPlayer> getOnlinePlayers() { public List<APIPlayer> getOnlinePlayers() {
return Bukkit.getOnlinePlayers().stream() return Bukkit.getOnlinePlayers().stream()
.map(pl -> cachedPlayers.computeIfAbsent(pl.getUniqueId(), k -> new BukkitPlayer(pl))) .map(pl -> cachedPlayers.computeIfAbsent(pl.getUniqueId(), k -> new BukkitPlayer(pl)))
.collect(Collectors.toList()); .collect(Collectors.toList());
} }
} }
@@ -24,11 +24,6 @@ import dev.brighten.antivpn.database.local.H2VPN;
import dev.brighten.antivpn.database.mongo.MongoVPN; import dev.brighten.antivpn.database.mongo.MongoVPN;
import dev.brighten.antivpn.database.sql.MySqlVPN; import dev.brighten.antivpn.database.sql.MySqlVPN;
import dev.brighten.antivpn.loader.LoaderBootstrap; import dev.brighten.antivpn.loader.LoaderBootstrap;
import java.io.File;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import lombok.Getter; import lombok.Getter;
import org.bstats.bukkit.Metrics; import org.bstats.bukkit.Metrics;
import org.bstats.charts.SimplePie; import org.bstats.charts.SimplePie;
@@ -39,124 +34,129 @@ import org.bukkit.plugin.SimplePluginManager;
import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.plugin.java.JavaPlugin;
import org.bukkit.scheduler.BukkitRunnable; import org.bukkit.scheduler.BukkitRunnable;
import java.io.File;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class BukkitPlugin implements LoaderBootstrap { public class BukkitPlugin implements LoaderBootstrap {
public static BukkitPlugin pluginInstance; public static BukkitPlugin pluginInstance;
private SimpleCommandMap commandMap; private SimpleCommandMap commandMap;
@Getter private File dataFolder; @Getter
private final List<org.bukkit.command.Command> registeredCommands = new ArrayList<>(); private File dataFolder;
@Getter private final JavaPlugin plugin; private final List<org.bukkit.command.Command> registeredCommands = new ArrayList<>();
@Getter
private final JavaPlugin plugin;
public BukkitPlugin(JavaPlugin plugin) { public BukkitPlugin(JavaPlugin plugin) {
this.plugin = plugin; this.plugin = plugin;
} }
@Getter private PlayerCommandRunner playerCommandRunner; @Getter
private PlayerCommandRunner playerCommandRunner;
@Override @Override
public void onLoad(File dataFolder) { public void onLoad(File dataFolder) {
this.dataFolder = dataFolder; this.dataFolder = dataFolder;
} }
public void onEnable() { public void onEnable() {
pluginInstance = this; pluginInstance = this;
Bukkit.getLogger().info("Starting AntiVPN services..."); Bukkit.getLogger().info("Starting AntiVPN services...");
AntiVPN.start(new BukkitListener(), new BukkitPlayerExecutor(), getDataFolder()); AntiVPN.start(new BukkitListener(), new BukkitPlayerExecutor(), getDataFolder());
playerCommandRunner = new PlayerCommandRunner(); playerCommandRunner = new PlayerCommandRunner();
playerCommandRunner.start(); playerCommandRunner.start();
// Loading our bStats metrics to be pushed to https://bstats.org // Loading our bStats metrics to be pushed to https://bstats.org
if (AntiVPN.getInstance().getVpnConfig().metrics()) { if(AntiVPN.getInstance().getVpnConfig().metrics()) {
Bukkit.getLogger().info("Starting bStats metrics..."); Bukkit.getLogger().info("Starting bStats metrics...");
Metrics metrics = new Metrics(plugin, 12615); Metrics metrics = new Metrics(plugin, 12615);
metrics.addCustomChart(new SimplePie("database_used", this::getDatabaseType)); metrics.addCustomChart(new SimplePie("database_used", this::getDatabaseType));
new BukkitRunnable() { new BukkitRunnable() {
public void run() { public void run() {
AntiVPN.getInstance().checked = AntiVPN.getInstance().detections = 0; AntiVPN.getInstance().checked = AntiVPN.getInstance().detections = 0;
}
}.runTaskTimerAsynchronously(plugin, 12000, 12000);
} }
}.runTaskTimerAsynchronously(plugin, 12000, 12000);
Bukkit.getLogger().info("Setting up and registering commands...");
// We need access to the commandMap to register our commands without using the "proper" method
if (Bukkit.getServer().getPluginManager() instanceof SimplePluginManager manager) {
try {
Field field = SimplePluginManager.class.getDeclaredField("commandMap");
field.setAccessible(true);
commandMap = (SimpleCommandMap) field.get(manager);
} catch (IllegalArgumentException | SecurityException | NoSuchFieldException | IllegalAccessException e) {
AntiVPN.getInstance().getExecutor().logException(e);
}
}
// Registering commands
for (Command command : AntiVPN.getInstance().getCommands()) {
// Wraps our general command API to Bukkit specific calls
BukkitCommand newCommand = new BukkitCommand(command);
// Adding to our own list for later referencing
registeredCommands.add(newCommand);
// This tells Bukkit to register our command for use.
commandMap.register(plugin.getName(), newCommand);
}
//TODO Finish system before implementing on startup
/*Bukkit.getLogger().info("Getting strings...");
AntiVPN.getInstance().getMessageHandler().initStrings(vpnString -> new ConfigDefault<>
(vpnString.getDefaultMessage(), "messages." + vpnString.getKey(), BukkitPlugin.pluginInstance)
.get());
AntiVPN.getInstance().getMessageHandler().reloadStrings();*/
plugin.reloadConfig();
} }
Bukkit.getLogger().info("Setting up and registering commands..."); @Override
// We need access to the commandMap to register our commands without using the "proper" method @SuppressWarnings("unchecked")
if (Bukkit.getServer().getPluginManager() instanceof SimplePluginManager manager) { public void onDisable() {
try { Bukkit.getLogger().info("Stopping plugin services...");
Field field = SimplePluginManager.class.getDeclaredField("commandMap"); AntiVPN.getInstance().stop();
field.setAccessible(true); playerCommandRunner.stop();
commandMap = (SimpleCommandMap) field.get(manager);
} catch (IllegalArgumentException Bukkit.getLogger().info("Unregistering commands...");
| SecurityException try {
| NoSuchFieldException Field field = SimpleCommandMap.class.getDeclaredField("knownCommands");
| IllegalAccessException e) { field.setAccessible(true);
AntiVPN.getInstance().getExecutor().logException(e);
} if(field.get(commandMap) instanceof Map<?, ?> knownCommands) {
Map<String, org.bukkit.command.Command> casted = (Map<String, org.bukkit.command.Command>) knownCommands;
casted.values().removeAll(registeredCommands);
registeredCommands.clear();
}
} catch (IllegalAccessException | NoSuchFieldException e) {
AntiVPN.getInstance().getExecutor().logException(e);
}
Bukkit.getLogger().info("Unregistering listeners...");
HandlerList.unregisterAll(plugin);
Bukkit.getLogger().info("Cancelling any running tasks...");
Bukkit.getScheduler().cancelTasks(plugin);
} }
// Registering commands private String getDatabaseType() {
for (Command command : AntiVPN.getInstance().getCommands()) { VPNDatabase database = AntiVPN.getInstance().getDatabase();
// Wraps our general command API to Bukkit specific calls
BukkitCommand newCommand = new BukkitCommand(command);
// Adding to our own list for later referencing if(database instanceof MySqlVPN) {
registeredCommands.add(newCommand); return "MySQL";
} else if(database instanceof H2VPN) {
// This tells Bukkit to register our command for use. return "H2";
commandMap.register(plugin.getName(), newCommand); } else if(database instanceof MongoVPN) {
return "MongoDB";
} else {
return "No-Database";
}
} }
// TODO Finish system before implementing on startup
/*Bukkit.getLogger().info("Getting strings...");
AntiVPN.getInstance().getMessageHandler().initStrings(vpnString -> new ConfigDefault<>
(vpnString.getDefaultMessage(), "messages." + vpnString.getKey(), BukkitPlugin.pluginInstance)
.get());
AntiVPN.getInstance().getMessageHandler().reloadStrings();*/
plugin.reloadConfig();
}
@Override
@SuppressWarnings("unchecked")
public void onDisable() {
Bukkit.getLogger().info("Stopping plugin services...");
AntiVPN.getInstance().stop();
playerCommandRunner.stop();
Bukkit.getLogger().info("Unregistering commands...");
try {
Field field = SimpleCommandMap.class.getDeclaredField("knownCommands");
field.setAccessible(true);
if (field.get(commandMap) instanceof Map<?, ?> knownCommands) {
Map<String, org.bukkit.command.Command> casted =
(Map<String, org.bukkit.command.Command>) knownCommands;
casted.values().removeAll(registeredCommands);
registeredCommands.clear();
}
} catch (IllegalAccessException | NoSuchFieldException e) {
AntiVPN.getInstance().getExecutor().logException(e);
}
Bukkit.getLogger().info("Unregistering listeners...");
HandlerList.unregisterAll(plugin);
Bukkit.getLogger().info("Cancelling any running tasks...");
Bukkit.getScheduler().cancelTasks(plugin);
}
private String getDatabaseType() {
VPNDatabase database = AntiVPN.getInstance().getDatabase();
if (database instanceof MySqlVPN) {
return "MySQL";
} else if (database instanceof H2VPN) {
return "H2";
} else if (database instanceof MongoVPN) {
return "MongoDB";
} else {
return "No-Database";
}
}
} }
@@ -17,64 +17,61 @@
package dev.brighten.antivpn.bukkit; package dev.brighten.antivpn.bukkit;
import dev.brighten.antivpn.utils.MiscUtils; import dev.brighten.antivpn.utils.MiscUtils;
import lombok.Data;
import org.bukkit.Bukkit;
import org.bukkit.scheduler.BukkitRunnable;
import java.util.Queue; import java.util.Queue;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import lombok.Data;
import org.bukkit.Bukkit;
import org.bukkit.scheduler.BukkitRunnable;
public class PlayerCommandRunner { public class PlayerCommandRunner {
private final ScheduledExecutorService executorService; private final ScheduledExecutorService executorService;
private final Queue<PlayerAction> playerActions = new ArrayBlockingQueue<>(10000); private final Queue<PlayerAction> playerActions = new ArrayBlockingQueue<>(10000);
public PlayerCommandRunner() { public PlayerCommandRunner() {
executorService = executorService = Executors.newSingleThreadScheduledExecutor(
Executors.newSingleThreadScheduledExecutor( MiscUtils.createThreadFactory("AntiVPN:PlayerCommandRunner")
MiscUtils.createThreadFactory("AntiVPN:PlayerCommandRunner")); );
} }
void start() { void start() {
executorService.scheduleAtFixedRate( executorService.scheduleAtFixedRate(() -> {
() -> { long currentTime = System.currentTimeMillis();
long currentTime = System.currentTimeMillis(); while(!playerActions.isEmpty()) {
while (!playerActions.isEmpty()) { PlayerAction action = playerActions.peek();
PlayerAction action = playerActions.peek();
if (action == null) continue; if(action == null) continue;
if (currentTime - action.start > 2000L || Bukkit.getPlayer(action.getUuid()) != null) { if(currentTime - action.start > 2000L || Bukkit.getPlayer(action.getUuid()) != null) {
new BukkitRunnable() { new BukkitRunnable() {
public void run() { public void run() {
action.getAction().run(); action.getAction().run();
}
}.runTask(BukkitPlugin.pluginInstance.getPlugin());
playerActions.poll();
} }
}.runTask(BukkitPlugin.pluginInstance.getPlugin());
playerActions.poll();
} }
} }, 1000, 100, TimeUnit.MILLISECONDS);
}, }
1000,
100,
TimeUnit.MILLISECONDS);
}
void stop() { void stop() {
executorService.shutdown(); executorService.shutdown();
playerActions.clear(); playerActions.clear();
} }
void addAction(UUID uuid, Runnable action) { void addAction(UUID uuid, Runnable action) {
playerActions.add(new PlayerAction(uuid, System.currentTimeMillis(), action)); playerActions.add(new PlayerAction(uuid, System.currentTimeMillis(), action));
} }
@Data @Data
static class PlayerAction { static class PlayerAction {
private final UUID uuid; private final UUID uuid;
private final long start; private final long start;
private final Runnable action; private final Runnable action;
} }
} }
@@ -19,90 +19,75 @@ package dev.brighten.antivpn.bukkit.command;
import dev.brighten.antivpn.AntiVPN; import dev.brighten.antivpn.AntiVPN;
import dev.brighten.antivpn.bukkit.BukkitCommandExecutor; import dev.brighten.antivpn.bukkit.BukkitCommandExecutor;
import dev.brighten.antivpn.command.Command; import dev.brighten.antivpn.command.Command;
import java.util.Arrays;
import java.util.List;
import java.util.stream.IntStream;
import lombok.val; import lombok.val;
import net.md_5.bungee.api.ChatColor; import net.md_5.bungee.api.ChatColor;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import java.util.Arrays;
import java.util.List;
import java.util.stream.IntStream;
public class BukkitCommand extends org.bukkit.command.Command { public class BukkitCommand extends org.bukkit.command.Command {
private final Command command; private final Command command;
public BukkitCommand(Command command) {
super(command.name(), command.description(), command.usage(), Arrays.asList(command.aliases()));
public BukkitCommand(Command command) { this.command = command;
super(command.name(), command.description(), command.usage(), Arrays.asList(command.aliases())); }
this.command = command; @Override
} public List<String> tabComplete(CommandSender sender, String alias, String[] args)
throws IllegalArgumentException {
val children = command.children();
@Override if(children.length > 0 && args.length > 0) {
public List<String> tabComplete(CommandSender sender, String alias, String[] args) for (Command child : children) {
throws IllegalArgumentException { if(child.name().equalsIgnoreCase(args[0]) || Arrays.stream(child.aliases())
val children = command.children(); .anyMatch(alias2 -> alias2.equalsIgnoreCase(args[0]))) {
return child.tabComplete(new BukkitCommandExecutor(sender), alias, IntStream
if (children.length > 0 && args.length > 0) { .range(0, args.length - 1)
for (Command child : children) { .mapToObj(i -> args[i + 1]).toArray(String[]::new));
if (child.name().equalsIgnoreCase(args[0]) }
|| Arrays.stream(child.aliases()) }
.anyMatch(alias2 -> alias2.equalsIgnoreCase(args[0]))) {
return child.tabComplete(
new BukkitCommandExecutor(sender),
alias,
IntStream.range(0, args.length - 1)
.mapToObj(i -> args[i + 1])
.toArray(String[]::new));
} }
} return command.tabComplete(new BukkitCommandExecutor(sender), alias, args);
}
return command.tabComplete(new BukkitCommandExecutor(sender), alias, args);
}
@Override
public boolean execute(CommandSender sender, String s, String[] args) {
if (!sender.hasPermission("antivpn.command.*") && !sender.hasPermission(command.permission())) {
sender.sendMessage(
ChatColor.translateAlternateColorCodes(
'&',
AntiVPN.getInstance().getMessageHandler().getString("no-permission").getMessage()));
return true;
} }
val children = command.children(); @Override
public boolean execute(CommandSender sender, String s, String[] args) {
if (children.length > 0 && args.length > 0) { if(!sender.hasPermission("antivpn.command.*")
for (Command child : children) { && !sender.hasPermission(command.permission())) {
if (child.name().equalsIgnoreCase(args[0]) sender.sendMessage(ChatColor.translateAlternateColorCodes('&',
|| Arrays.stream(child.aliases()).anyMatch(alias -> alias.equalsIgnoreCase(args[0]))) { AntiVPN.getInstance().getMessageHandler().getString("no-permission").getMessage()));
if (!sender.hasPermission("antivpn.command.*")
&& !sender.hasPermission(child.permission())) {
sender.sendMessage(
ChatColor.translateAlternateColorCodes(
'&',
AntiVPN.getInstance()
.getMessageHandler()
.getString("no-permission")
.getMessage()));
return true; return true;
}
sender.sendMessage(
ChatColor.translateAlternateColorCodes(
'&',
child.execute(
new BukkitCommandExecutor(sender),
IntStream.range(0, args.length - 1)
.mapToObj(i -> args[i + 1])
.toArray(String[]::new))));
return true;
} }
}
val children = command.children();
if(children.length > 0 && args.length > 0) {
for (Command child : children) {
if(child.name().equalsIgnoreCase(args[0]) || Arrays.stream(child.aliases())
.anyMatch(alias -> alias.equalsIgnoreCase(args[0]))) {
if(!sender.hasPermission("antivpn.command.*")
&& !sender.hasPermission(child.permission())) {
sender.sendMessage(ChatColor.translateAlternateColorCodes('&',
AntiVPN.getInstance().getMessageHandler().getString("no-permission").getMessage()));
return true;
}
sender.sendMessage(ChatColor.translateAlternateColorCodes('&',
child.execute(new BukkitCommandExecutor(sender), IntStream
.range(0, args.length - 1)
.mapToObj(i -> args[i + 1]).toArray(String[]::new))));
return true;
}
}
}
sender.sendMessage(ChatColor.translateAlternateColorCodes('&',
command.execute(new BukkitCommandExecutor(sender), args)));
return true;
} }
sender.sendMessage(
ChatColor.translateAlternateColorCodes(
'&', command.execute(new BukkitCommandExecutor(sender), args)));
return true;
}
} }
@@ -1,20 +1,24 @@
package dev.brighten.antivpn.bukkit; package dev.brighten.antivpn.bukkit;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.*;
import be.seeseemelk.mockbukkit.MockBukkit; import be.seeseemelk.mockbukkit.MockBukkit;
import be.seeseemelk.mockbukkit.ServerMock; import be.seeseemelk.mockbukkit.ServerMock;
import be.seeseemelk.mockbukkit.entity.PlayerMock; import be.seeseemelk.mockbukkit.entity.PlayerMock;
import dev.brighten.antivpn.AntiVPN; import dev.brighten.antivpn.AntiVPN;
import dev.brighten.antivpn.StandardTest; import dev.brighten.antivpn.api.PlayerExecutor;
import dev.brighten.antivpn.api.*; import dev.brighten.antivpn.api.VPNConfig;
import dev.brighten.antivpn.api.VPNExecutor;
import dev.brighten.antivpn.message.MessageHandler; import dev.brighten.antivpn.message.MessageHandler;
import dev.brighten.antivpn.message.VpnString; import dev.brighten.antivpn.message.VpnString;
import dev.brighten.antivpn.web.objects.VPNResponse; import dev.brighten.antivpn.web.objects.VPNResponse;
import org.bukkit.event.player.PlayerLoginEvent;
import org.bukkit.command.CommandSender;
import org.bukkit.plugin.PluginDescriptionFile;
import org.bukkit.plugin.java.JavaPlugin;
import org.jetbrains.annotations.NotNull;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.net.InetAddress; import java.net.InetAddress;
import java.util.Optional; import java.util.Optional;
@@ -25,179 +29,172 @@ import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
import org.bukkit.command.CommandSender;
import org.bukkit.event.player.PlayerLoginEvent;
import org.bukkit.plugin.PluginDescriptionFile;
import org.bukkit.plugin.java.JavaPlugin;
import org.jetbrains.annotations.NotNull;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
public class BukkitListenerTest extends StandardTest { import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.*;
private ServerMock server; public class BukkitListenerTest {
private BukkitListener listener;
private VPNExecutor vpnExecutor;
@BeforeEach private ServerMock server;
public void setUp() throws Exception { private BukkitListener listener;
server = MockBukkit.mock(new RecordingServerMock()); private VPNExecutor vpnExecutor;
JavaPlugin plugin =
MockBukkit.loadWith(
TestPlugin.class,
new PluginDescriptionFile("AntiVPNTest", "1.0.0", TestPlugin.class.getName()));
BukkitPlugin.pluginInstance = new BukkitPlugin(plugin);
AntiVPN antiVPN = mock(AntiVPN.class); @BeforeEach
VPNConfig config = mock(VPNConfig.class); public void setUp() throws Exception {
PlayerExecutor playerExecutor = mock(PlayerExecutor.class); server = MockBukkit.mock(new RecordingServerMock());
vpnExecutor = mock(VPNExecutor.class); JavaPlugin plugin = MockBukkit.loadWith(
MessageHandler messageHandler = mock(MessageHandler.class); TestPlugin.class,
new PluginDescriptionFile("AntiVPNTest", "1.0.0", TestPlugin.class.getName())
);
BukkitPlugin.pluginInstance = new BukkitPlugin(plugin);
when(antiVPN.getVpnConfig()).thenReturn(config); AntiVPN antiVPN = mock(AntiVPN.class);
when(antiVPN.getPlayerExecutor()).thenReturn(playerExecutor); VPNConfig config = mock(VPNConfig.class);
when(antiVPN.getExecutor()).thenReturn(vpnExecutor); PlayerExecutor playerExecutor = mock(PlayerExecutor.class);
when(antiVPN.getMessageHandler()).thenReturn(messageHandler); vpnExecutor = mock(VPNExecutor.class);
MessageHandler messageHandler = mock(MessageHandler.class);
when(playerExecutor.getPlayer(any(UUID.class))).thenReturn(Optional.empty()); when(antiVPN.getVpnConfig()).thenReturn(config);
when(config.getPrefixWhitelists()).thenReturn(java.util.Collections.emptyList()); when(antiVPN.getPlayerExecutor()).thenReturn(playerExecutor);
when(config.getCountryList()).thenReturn(java.util.Collections.emptyList()); when(antiVPN.getExecutor()).thenReturn(vpnExecutor);
when(config.isKickPlayers()).thenReturn(true); when(antiVPN.getMessageHandler()).thenReturn(messageHandler);
when(config.getKickMessage()).thenReturn("Blocked!");
VpnString mockVpnString = mock(VpnString.class); when(playerExecutor.getPlayer(any(UUID.class))).thenReturn(Optional.empty());
when(mockVpnString.getFormattedMessage(any())).thenReturn("Blocked!"); when(config.getPrefixWhitelists()).thenReturn(java.util.Collections.emptyList());
when(messageHandler.getString(anyString())).thenReturn(mockVpnString); when(config.getCountryList()).thenReturn(java.util.Collections.emptyList());
when(config.isKickPlayers()).thenReturn(true);
when(config.getKickMessage()).thenReturn("Blocked!");
when(vpnExecutor.checkIp(anyString())) VpnString mockVpnString = mock(VpnString.class);
.thenReturn( when(mockVpnString.getFormattedMessage(any())).thenReturn("Blocked!");
CompletableFuture.completedFuture( when(messageHandler.getString(anyString())).thenReturn(mockVpnString);
VPNResponse.builder()
.success(true)
.proxy(false)
.ip("127.0.0.1")
.method("N/A")
.countryName("N/A")
.city("N/A")
.build()));
// Use reflection to set the private static INSTANCE field when(vpnExecutor.checkIp(anyString())).thenReturn(CompletableFuture.completedFuture(
Field instanceField = AntiVPN.class.getDeclaredField("INSTANCE"); VPNResponse.builder().success(true).proxy(false).ip("127.0.0.1")
instanceField.setAccessible(true); .method("N/A").countryName("N/A").city("N/A").build()
instanceField.set(null, antiVPN); ));
listener = new BukkitListener(); // Use reflection to set the private static INSTANCE field
} Field instanceField = AntiVPN.class.getDeclaredField("INSTANCE");
instanceField.setAccessible(true);
instanceField.set(null, antiVPN);
@AfterEach listener = new BukkitListener();
public void tearDown() throws Exception {
// Reset the singleton
Field instanceField = AntiVPN.class.getDeclaredField("INSTANCE");
instanceField.setAccessible(true);
instanceField.set(null, null);
BukkitPlugin.pluginInstance = null;
MockBukkit.unmock();
}
@Test
public void testLoginEventAllowed() throws Exception {
PlayerMock player = server.addPlayer("TestPlayer");
InetAddress address = InetAddress.getByName("127.0.0.1");
mockCache(
"127.0.0.1",
new CheckResult(
VPNResponse.builder()
.success(true)
.proxy(false)
.ip("127.0.0.1")
.method("N/A")
.countryName("N/A")
.countryCode("N/A")
.city("N/A")
.build(),
ResultType.ALLOWED,
true));
PlayerLoginEvent event = new PlayerLoginEvent(player, "localhost", address);
listener.onLogin(event);
assertEquals(PlayerLoginEvent.Result.ALLOWED, event.getResult());
}
@Test
public void testLoginPipelineProxyPlayerIsKickedWithoutErrors() throws Exception {
PlayerMock player = server.addPlayer("PipelineProxyPlayer");
InetAddress address = InetAddress.getByName("1.1.1.1");
mockCache();
PlayerLoginEvent event = new PlayerLoginEvent(player, "localhost", address);
assertDoesNotThrow(() -> listener.onLogin(event));
assertEquals(PlayerLoginEvent.Result.KICK_BANNED, event.getResult());
assertEquals(
"Blocked!",
net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection()
.serialize(event.kickMessage()));
}
@Test
public void testRunCommandDispatchesOnPrimaryThreadWhenCalledAsynchronously() {
RecordingServerMock recordingServer = (RecordingServerMock) server;
ExecutorService executor = Executors.newSingleThreadExecutor();
try {
CompletableFuture<Void> asyncCall =
CompletableFuture.runAsync(() -> listener.runCommand("antivpn-test &aok"), executor);
assertDoesNotThrow(() -> asyncCall.get(5, TimeUnit.SECONDS));
assertFalse(
recordingServer.commandDispatched(),
"Command should be scheduled, not dispatched asynchronously");
server.getScheduler().performOneTick();
assertTrue(
recordingServer.commandDispatched(),
"Scheduled command should be dispatched on the next server tick");
assertTrue(
recordingServer.dispatchedOnPrimaryThread(),
"Command dispatch must happen on Bukkit's primary thread");
assertEquals("antivpn-test §aok", recordingServer.dispatchedCommand());
} finally {
executor.shutdownNow();
}
}
public static class TestPlugin extends JavaPlugin {}
private static class RecordingServerMock extends ServerMock {
private final AtomicBoolean commandDispatched = new AtomicBoolean();
private final AtomicBoolean dispatchedOnPrimaryThread = new AtomicBoolean();
private final AtomicReference<String> dispatchedCommand = new AtomicReference<>();
@Override
public boolean dispatchCommand(@NotNull CommandSender sender, @NotNull String commandLine) {
commandDispatched.set(true);
dispatchedOnPrimaryThread.set(isPrimaryThread());
dispatchedCommand.set(commandLine);
return super.dispatchCommand(sender, commandLine);
} }
private boolean commandDispatched() { @AfterEach
return commandDispatched.get(); public void tearDown() throws Exception {
// Reset the singleton
Field instanceField = AntiVPN.class.getDeclaredField("INSTANCE");
instanceField.setAccessible(true);
instanceField.set(null, null);
BukkitPlugin.pluginInstance = null;
MockBukkit.unmock();
} }
private boolean dispatchedOnPrimaryThread() { @Test
return dispatchedOnPrimaryThread.get(); public void testLoginEventAllowed() throws Exception {
PlayerMock player = server.addPlayer("TestPlayer");
InetAddress address = InetAddress.getByName("127.0.0.1");
PlayerLoginEvent event = new PlayerLoginEvent(player, "localhost", address);
listener.onLogin(event);
assertEquals(PlayerLoginEvent.Result.ALLOWED, event.getResult());
} }
private String dispatchedCommand() { @Test
return dispatchedCommand.get(); public void testLoginEventBlocked() throws Exception {
PlayerMock player = server.addPlayer("ProxyPlayer");
InetAddress address = InetAddress.getByName("1.1.1.1");
// Mock proxy response
when(vpnExecutor.checkIp("1.1.1.1")).thenReturn(CompletableFuture.completedFuture(
VPNResponse.builder().success(true).proxy(true).ip("1.1.1.1")
.method("N/A").countryName("N/A").countryCode("N/A").city("N/A").build()
));
PlayerLoginEvent event = new PlayerLoginEvent(player, "localhost", address);
listener.onLogin(event);
assertEquals(PlayerLoginEvent.Result.KICK_BANNED, event.getResult());
assertEquals("Blocked!", net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().serialize(event.kickMessage()));
}
@Test
public void testLoginPipelineProxyPlayerIsKickedWithoutErrors() throws Exception {
PlayerMock player = server.addPlayer("PipelineProxyPlayer");
InetAddress address = InetAddress.getByName("1.1.1.1");
when(vpnExecutor.checkIp("1.1.1.1")).thenReturn(CompletableFuture.completedFuture(
VPNResponse.builder().success(true).proxy(true).ip("1.1.1.1")
.method("N/A").countryName("N/A").countryCode("N/A").city("N/A").build()
));
PlayerLoginEvent event = new PlayerLoginEvent(player, "localhost", address);
assertDoesNotThrow(() -> listener.onLogin(event));
assertEquals(PlayerLoginEvent.Result.KICK_BANNED, event.getResult());
assertEquals("Blocked!", net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().serialize(event.kickMessage()));
}
@Test
public void testRunCommandDispatchesOnPrimaryThreadWhenCalledAsynchronously() {
RecordingServerMock recordingServer = (RecordingServerMock) server;
ExecutorService executor = Executors.newSingleThreadExecutor();
try {
CompletableFuture<Void> asyncCall = CompletableFuture.runAsync(
() -> listener.runCommand("antivpn-test &aok"),
executor
);
assertDoesNotThrow(() -> asyncCall.get(5, TimeUnit.SECONDS));
assertFalse(recordingServer.commandDispatched(), "Command should be scheduled, not dispatched asynchronously");
server.getScheduler().performOneTick();
assertTrue(recordingServer.commandDispatched(), "Scheduled command should be dispatched on the next server tick");
assertTrue(recordingServer.dispatchedOnPrimaryThread(), "Command dispatch must happen on Bukkit's primary thread");
assertEquals("antivpn-test §aok", recordingServer.dispatchedCommand());
} finally {
executor.shutdownNow();
}
}
public static class TestPlugin extends JavaPlugin {
}
private static class RecordingServerMock extends ServerMock {
private final AtomicBoolean commandDispatched = new AtomicBoolean();
private final AtomicBoolean dispatchedOnPrimaryThread = new AtomicBoolean();
private final AtomicReference<String> dispatchedCommand = new AtomicReference<>();
@Override
public boolean dispatchCommand(@NotNull CommandSender sender, @NotNull String commandLine) {
commandDispatched.set(true);
dispatchedOnPrimaryThread.set(isPrimaryThread());
dispatchedCommand.set(commandLine);
return super.dispatchCommand(sender, commandLine);
}
private boolean commandDispatched() {
return commandDispatched.get();
}
private boolean dispatchedOnPrimaryThread() {
return dispatchedOnPrimaryThread.get();
}
private String dispatchedCommand() {
return dispatchedCommand.get();
}
} }
}
} }
@@ -1,56 +1,56 @@
package dev.brighten.antivpn.bukkit; package dev.brighten.antivpn.bukkit;
import be.seeseemelk.mockbukkit.MockBukkit;
import be.seeseemelk.mockbukkit.ServerMock;
import be.seeseemelk.mockbukkit.entity.PlayerMock;
import org.bukkit.plugin.java.JavaPlugin;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import be.seeseemelk.mockbukkit.MockBukkit;
import be.seeseemelk.mockbukkit.ServerMock;
import be.seeseemelk.mockbukkit.entity.PlayerMock;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import org.bukkit.plugin.java.JavaPlugin;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
class BukkitPlayerTest { class BukkitPlayerTest {
private ServerMock server; private ServerMock server;
@BeforeEach @BeforeEach
void setUp() { void setUp() {
server = MockBukkit.mock(); server = MockBukkit.mock();
BukkitPlugin pluginBootstrap = mock(BukkitPlugin.class); BukkitPlugin pluginBootstrap = mock(BukkitPlugin.class);
JavaPlugin plugin = MockBukkit.createMockPlugin(); JavaPlugin plugin = MockBukkit.createMockPlugin();
when(pluginBootstrap.getPlugin()).thenReturn(plugin); when(pluginBootstrap.getPlugin()).thenReturn(plugin);
BukkitPlugin.pluginInstance = pluginBootstrap; BukkitPlugin.pluginInstance = pluginBootstrap;
} }
@AfterEach @AfterEach
void tearDown() { void tearDown() {
BukkitPlugin.pluginInstance = null; BukkitPlugin.pluginInstance = null;
MockBukkit.unmock(); MockBukkit.unmock();
} }
@Test @Test
void kickPlayerCalledFromAsyncContext_isScheduledAndExecutedOnMainThread() { void kickPlayerCalledFromAsyncContext_isScheduledAndExecutedOnMainThread() {
PlayerMock player = server.addPlayer("AsyncKickPlayer"); PlayerMock player = server.addPlayer("AsyncKickPlayer");
BukkitPlayer bukkitPlayer = new BukkitPlayer(player); BukkitPlayer bukkitPlayer = new BukkitPlayer(player);
assertTrue(player.isOnline()); assertTrue(player.isOnline());
CompletableFuture<Void> asyncKick = CompletableFuture<Void> asyncKick = CompletableFuture.runAsync(() -> bukkitPlayer.kickPlayer("&cBlocked!"));
CompletableFuture.runAsync(() -> bukkitPlayer.kickPlayer("&cBlocked!")); assertDoesNotThrow(() -> asyncKick.get(1, TimeUnit.SECONDS));
assertDoesNotThrow(() -> asyncKick.get(1, TimeUnit.SECONDS));
assertTrue(player.isOnline(), "Kick should be deferred to the server scheduler"); assertTrue(player.isOnline(), "Kick should be deferred to the server scheduler");
server.getScheduler().performTicks(1); server.getScheduler().performTicks(1);
assertFalse(player.isOnline(), "Player should be kicked when scheduled task runs"); assertFalse(player.isOnline(), "Player should be kicked when scheduled task runs");
} }
} }
-10
View File
@@ -10,12 +10,6 @@ dependencies {
implementation project(':Common:loader-utils') implementation project(':Common:loader-utils')
} }
tasks.processResources {
filesMatching('bungee.yml') {
expand(project: project, projectVersion: project.version)
}
}
shadowJar { shadowJar {
archiveClassifier.set('') archiveClassifier.set('')
@@ -29,8 +23,4 @@ tasks.named('shadowJar') {
dependsOn(':Bungee:BungeePlugin:shadowJar') dependsOn(':Bungee:BungeePlugin:shadowJar')
} }
tasks.named('compileJava') {
dependsOn(':Bungee:BungeePlugin:shadowJar')
}
tasks.build.dependsOn shadowJar tasks.build.dependsOn shadowJar
@@ -22,30 +22,31 @@ import net.md_5.bungee.api.plugin.Plugin;
public class BungeeLoaderPlugin extends Plugin { public class BungeeLoaderPlugin extends Plugin {
private static final String JAR_NAME = "antivpn-bungee.jarinjar"; private static final String JAR_NAME = "antivpn-bungee.jarinjar";
private static final String SOURCE_NAME = "antivpn-source.jarinjar"; private static final String SOURCE_NAME = "antivpn-source.jarinjar";
private static final String BOOTSTRAP_CLASS = "dev.brighten.antivpn.bungee.BungeePlugin"; private static final String BOOTSTRAP_CLASS = "dev.brighten.antivpn.bungee.BungeePlugin";
private final LoaderBootstrap plugin; private final LoaderBootstrap plugin;
public BungeeLoaderPlugin() { public BungeeLoaderPlugin() {
JarInJarClassLoader loader = JarInJarClassLoader loader = new JarInJarClassLoader(getClass().getClassLoader(), JAR_NAME, SOURCE_NAME);
new JarInJarClassLoader(getClass().getClassLoader(), JAR_NAME, SOURCE_NAME); this.plugin = loader.instantiatePlugin(BOOTSTRAP_CLASS, Plugin.class, this);
this.plugin = loader.instantiatePlugin(BOOTSTRAP_CLASS, Plugin.class, this); }
}
@Override @Override
public void onLoad() { public void onLoad() {
this.plugin.onLoad(getDataFolder()); this.plugin.onLoad(getDataFolder());
} }
@Override
public void onEnable() {
this.plugin.onEnable();
}
@Override
public void onDisable() {
this.plugin.onDisable();
}
@Override
public void onEnable() {
this.plugin.onEnable();
}
@Override
public void onDisable() {
this.plugin.onDisable();
}
} }
-1
View File
@@ -13,7 +13,6 @@ dependencies {
testImplementation 'org.mockito:mockito-subclass:5.11.0' testImplementation 'org.mockito:mockito-subclass:5.11.0'
testImplementation 'org.mockito:mockito-junit-jupiter:5.11.0' testImplementation 'org.mockito:mockito-junit-jupiter:5.11.0'
testImplementation 'com.github.ben-manes.caffeine:caffeine:3.1.8' testImplementation 'com.github.ben-manes.caffeine:caffeine:3.1.8'
testImplementation testFixtures(project(':Common:Source'))
} }
tasks.compileJava.dependsOn(':Common:Source:jar') tasks.compileJava.dependsOn(':Common:Source:jar')
@@ -20,9 +20,6 @@ import dev.brighten.antivpn.AntiVPN;
import dev.brighten.antivpn.api.*; import dev.brighten.antivpn.api.*;
import dev.brighten.antivpn.utils.MiscUtils; import dev.brighten.antivpn.utils.MiscUtils;
import dev.brighten.antivpn.utils.StringUtil; import dev.brighten.antivpn.utils.StringUtil;
import java.net.InetSocketAddress;
import java.util.UUID;
import java.util.logging.Level;
import net.md_5.bungee.api.chat.TextComponent; import net.md_5.bungee.api.chat.TextComponent;
import net.md_5.bungee.api.event.LoginEvent; import net.md_5.bungee.api.event.LoginEvent;
import net.md_5.bungee.api.event.PlayerDisconnectEvent; import net.md_5.bungee.api.event.PlayerDisconnectEvent;
@@ -32,123 +29,94 @@ import net.md_5.bungee.api.scheduler.ScheduledTask;
import net.md_5.bungee.event.EventHandler; import net.md_5.bungee.event.EventHandler;
import net.md_5.bungee.event.EventPriority; import net.md_5.bungee.event.EventPriority;
import java.net.InetSocketAddress;
import java.util.UUID;
import java.util.logging.Level;
public class BungeeListener extends VPNExecutor implements Listener { public class BungeeListener extends VPNExecutor implements Listener {
private ScheduledTask cacheResetTask; private ScheduledTask cacheResetTask;
@Override @Override
public void registerListeners() { public void registerListeners() {
BungeePlugin.pluginInstance BungeePlugin.pluginInstance.getProxy().getPluginManager()
.getProxy() .registerListener(BungeePlugin.pluginInstance.getPlugin(), this);
.getPluginManager()
.registerListener(BungeePlugin.pluginInstance.getPlugin(), this);
}
@Override
public void log(Level level, String log, Object... objects) {
BungeePlugin.pluginInstance.getProxy().getLogger().log(Level.INFO, String.format(log, objects));
}
@Override
public void log(String log, Object... objects) {
log(Level.INFO, log, objects);
}
@Override
public void logException(String message, Throwable ex) {
BungeePlugin.pluginInstance.getProxy().getLogger().log(Level.SEVERE, message, ex);
}
@Override
public void runCommand(String command) {
BungeePlugin.pluginInstance
.getProxy()
.getPluginManager()
.dispatchCommand(BungeePlugin.pluginInstance.getProxy().getConsole(), command);
}
@Override
public void disablePlugin() {
BungeePlugin.pluginInstance
.getProxy()
.getPluginManager()
.unregisterListeners(BungeePlugin.pluginInstance.getPlugin());
if (cacheResetTask != null) {
cacheResetTask.cancel();
cacheResetTask = null;
} }
BungeePlugin.pluginInstance
.getProxy()
.getPluginManager()
.unregisterCommands(BungeePlugin.pluginInstance.getPlugin());
BungeePlugin.pluginInstance.onDisable();
}
@EventHandler(priority = EventPriority.HIGH) @Override
public void onListener(final PreLoginEvent event) { public void log(Level level, String log, Object... objects) {
APIPlayer player = BungeePlugin.pluginInstance.getProxy().getLogger().log(Level.INFO, String.format(log, objects));
AntiVPN.getInstance() }
.getPlayerExecutor()
.getPlayer(event.getConnection().getUniqueId())
.orElseGet(
() -> {
UUID uuid = MiscUtils.lookupUUID(event.getConnection().getName());
AntiVPN.getInstance()
.getExecutor()
.log(
Level.INFO,
"Getting offline player for %s with name %s",
event.getConnection().getUniqueId(),
uuid);
return new OfflinePlayer( @Override
uuid, public void log(String log, Object... objects) {
event.getConnection().getName(), log(Level.INFO, log, objects);
((InetSocketAddress) event.getConnection().getSocketAddress()).getAddress()); }
@Override
public void logException(String message, Throwable ex) {
BungeePlugin.pluginInstance.getProxy().getLogger().log(Level.SEVERE, message, ex);
}
@Override
public void runCommand(String command) {
BungeePlugin.pluginInstance.getProxy().getPluginManager()
.dispatchCommand(BungeePlugin.pluginInstance.getProxy().getConsole(), command);
}
@Override
public void disablePlugin() {
BungeePlugin.pluginInstance.getProxy().getPluginManager().unregisterListeners(BungeePlugin.pluginInstance.getPlugin());
if (cacheResetTask != null) {
cacheResetTask.cancel();
cacheResetTask = null;
}
BungeePlugin.pluginInstance.getProxy().getPluginManager().unregisterCommands(BungeePlugin.pluginInstance.getPlugin());
BungeePlugin.pluginInstance.onDisable();
}
@EventHandler(priority = EventPriority.HIGH)
public void onListener(final PreLoginEvent event) {
APIPlayer player = AntiVPN.getInstance().getPlayerExecutor().getPlayer(event.getConnection().getUniqueId())
.orElseGet(() -> {
UUID uuid = MiscUtils.lookupUUID(event.getConnection().getName());
AntiVPN.getInstance().getExecutor().log(Level.INFO, "Getting offline player for %s with name %s",
event.getConnection().getUniqueId(), uuid);
return new OfflinePlayer(uuid, event.getConnection().getName(),
((InetSocketAddress) event.getConnection().getSocketAddress()).getAddress());
}); });
CheckResult result = player.checkPlayer(); player.checkPlayer(result -> {
if (!result.resultType().isShouldBlock()) return;
if (!result.resultType().isShouldBlock()) return; if(!AntiVPN.getInstance().getVpnConfig().isKickPlayers()) {
return;
}
if (!AntiVPN.getInstance().getVpnConfig().isKickPlayers()) { event.setCancelled(true);
return; event.setReason(TextComponent.fromLegacy(StringUtil.varReplace(switch (result.resultType()) {
case DENIED_PROXY -> StringUtil.varReplace(AntiVPN.getInstance().getVpnConfig()
.getKickMessage(), player, result.response());
case DENIED_COUNTRY -> StringUtil.varReplace(AntiVPN.getInstance().getVpnConfig()
.getCountryVanillaKickReason(), player, result.response());
default -> "You were kicked by KauriVPN for an unknown reason!";
}, player, result.response())));
});
} }
event.setCancelled(true); @EventHandler(priority = EventPriority.HIGH)
event.setReason( public void onJoin(LoginEvent event) {
TextComponent.fromLegacy( if(event.isCancelled()) return;
StringUtil.varReplace(
switch (result.resultType()) {
case DENIED_PROXY ->
StringUtil.varReplace(
AntiVPN.getInstance().getVpnConfig().getKickMessage(),
player,
result.response());
case DENIED_COUNTRY ->
StringUtil.varReplace(
AntiVPN.getInstance().getVpnConfig().getCountryVanillaKickReason(),
player,
result.response());
default -> "You were kicked by KauriVPN for an unknown reason!";
},
player,
result.response())));
}
@EventHandler(priority = EventPriority.HIGH) // Handling player alerts on join
public void onJoin(LoginEvent event) { AntiVPN.getInstance().getPlayerExecutor().getPlayer(event.getConnection().getUniqueId())
if (event.isCancelled()) return; .ifPresent(APIPlayer::checkAlertsState);
}
// Handling player alerts on join @EventHandler
AntiVPN.getInstance() public void onLeave(PlayerDisconnectEvent event) {
.getPlayerExecutor() AntiVPN.getInstance().getPlayerExecutor().unloadPlayer(event.getPlayer().getUniqueId());
.getPlayer(event.getConnection().getUniqueId()) }
.ifPresent(APIPlayer::checkAlertsState);
}
@EventHandler
public void onLeave(PlayerDisconnectEvent event) {
AntiVPN.getInstance().getPlayerExecutor().unloadPlayer(event.getPlayer().getUniqueId());
}
} }
@@ -23,28 +23,28 @@ import net.md_5.bungee.api.connection.ProxiedPlayer;
public class BungeePlayer extends APIPlayer { public class BungeePlayer extends APIPlayer {
private final ProxiedPlayer player; private final ProxiedPlayer player;
public BungeePlayer(ProxiedPlayer player) {
super(player.getUniqueId(), player.getName(), player.getAddress().getAddress());
public BungeePlayer(ProxiedPlayer player) { this.player = player;
super(player.getUniqueId(), player.getName(), player.getAddress().getAddress()); }
this.player = player;
}
@Override @Override
public void sendMessage(String message) { public void sendMessage(String message) {
player.sendMessage( player.sendMessage(TextComponent.fromLegacyText(ChatColor
TextComponent.fromLegacyText(ChatColor.translateAlternateColorCodes('&', message))); .translateAlternateColorCodes('&', message)));
} }
@Override @Override
public void kickPlayer(String reason) { public void kickPlayer(String reason) {
player.disconnect( player.disconnect(TextComponent.fromLegacyText(ChatColor
TextComponent.fromLegacyText(ChatColor.translateAlternateColorCodes('&', reason))); .translateAlternateColorCodes('&', reason)));
} }
@Override @Override
public boolean hasPermission(String permission) { public boolean hasPermission(String permission) {
return player.hasPermission(permission); return player.hasPermission(permission);
} }
} }
@@ -18,42 +18,42 @@ package dev.brighten.antivpn.bungee;
import dev.brighten.antivpn.api.APIPlayer; import dev.brighten.antivpn.api.APIPlayer;
import dev.brighten.antivpn.api.PlayerExecutor; import dev.brighten.antivpn.api.PlayerExecutor;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import java.util.*; import java.util.*;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import net.md_5.bungee.api.connection.ProxiedPlayer;
public class BungeePlayerExecutor implements PlayerExecutor { public class BungeePlayerExecutor implements PlayerExecutor {
private final Map<UUID, BungeePlayer> cachedPlayers = new HashMap<>(); private final Map<UUID, BungeePlayer> cachedPlayers = new HashMap<>();
@Override @Override
public Optional<APIPlayer> getPlayer(String name) { public Optional<APIPlayer> getPlayer(String name) {
ProxiedPlayer player = BungeePlugin.pluginInstance.getProxy().getPlayer(name); ProxiedPlayer player = BungeePlugin.pluginInstance.getProxy().getPlayer(name);
if (player == null) return Optional.empty(); if(player == null) return Optional.empty();
return Optional.of( return Optional.of(cachedPlayers.computeIfAbsent(player.getUniqueId(), key -> new BungeePlayer(player)));
cachedPlayers.computeIfAbsent(player.getUniqueId(), key -> new BungeePlayer(player))); }
}
@Override @Override
public Optional<APIPlayer> getPlayer(UUID uuid) { public Optional<APIPlayer> getPlayer(UUID uuid) {
ProxiedPlayer player = BungeePlugin.pluginInstance.getProxy().getPlayer(uuid); ProxiedPlayer player = BungeePlugin.pluginInstance.getProxy().getPlayer(uuid);
if (player == null) return Optional.empty(); if(player == null) return Optional.empty();
return Optional.of(cachedPlayers.computeIfAbsent(uuid, key -> new BungeePlayer(player))); return Optional.of(cachedPlayers.computeIfAbsent(uuid, key -> new BungeePlayer(player)));
} }
@Override @Override
public void unloadPlayer(UUID uuid) { public void unloadPlayer(UUID uuid) {
this.cachedPlayers.remove(uuid); this.cachedPlayers.remove(uuid);
} }
@Override @Override
public List<APIPlayer> getOnlinePlayers() { public List<APIPlayer> getOnlinePlayers() {
return BungeePlugin.pluginInstance.getProxy().getPlayers().stream() return BungeePlugin.pluginInstance.getProxy().getPlayers().stream()
.map(pl -> cachedPlayers.computeIfAbsent(pl.getUniqueId(), key -> new BungeePlayer(pl))) .map(pl -> cachedPlayers.computeIfAbsent(pl.getUniqueId(), key -> new BungeePlayer(pl)))
.collect(Collectors.toList()); .collect(Collectors.toList());
} }
} }
@@ -24,82 +24,79 @@ import dev.brighten.antivpn.database.local.H2VPN;
import dev.brighten.antivpn.database.mongo.MongoVPN; import dev.brighten.antivpn.database.mongo.MongoVPN;
import dev.brighten.antivpn.database.sql.MySqlVPN; import dev.brighten.antivpn.database.sql.MySqlVPN;
import dev.brighten.antivpn.loader.LoaderBootstrap; import dev.brighten.antivpn.loader.LoaderBootstrap;
import java.io.File;
import java.util.concurrent.TimeUnit;
import lombok.Getter; import lombok.Getter;
import net.md_5.bungee.api.ProxyServer; import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.plugin.Plugin; import net.md_5.bungee.api.plugin.Plugin;
import org.bstats.bungeecord.Metrics; import org.bstats.bungeecord.Metrics;
import org.bstats.charts.SimplePie; import org.bstats.charts.SimplePie;
import java.io.File;
import java.util.concurrent.TimeUnit;
public class BungeePlugin implements LoaderBootstrap { public class BungeePlugin implements LoaderBootstrap {
public static BungeePlugin pluginInstance; public static BungeePlugin pluginInstance;
@Getter private File dataFolder; @Getter
private File dataFolder;
@Getter private final Plugin plugin; @Getter
private final Plugin plugin;
public BungeePlugin(Plugin plugin) { public BungeePlugin(Plugin plugin) {
this.plugin = plugin; this.plugin = plugin;
}
@Override
public void onLoad(File dataFolder) {
this.dataFolder = dataFolder;
}
@Override
public void onEnable() {
pluginInstance = this;
// Setting up config
ProxyServer.getInstance().getLogger().info("Loading config...");
// Loading plugin
ProxyServer.getInstance().getLogger().info("Starting AntiVPN services...");
AntiVPN.start(new BungeeListener(), new BungeePlayerExecutor(), getDataFolder());
if (AntiVPN.getInstance().getVpnConfig().metrics()) {
ProxyServer.getInstance().getLogger().info("Starting bStats metrics...");
Metrics metrics = new Metrics(getPlugin(), 12616);
metrics.addCustomChart(new SimplePie("database_used", this::getDatabaseType));
ProxyServer.getInstance()
.getScheduler()
.schedule(
getPlugin(),
() -> AntiVPN.getInstance().checked = AntiVPN.getInstance().detections = 0,
10,
10,
TimeUnit.MINUTES);
} }
for (Command command : AntiVPN.getInstance().getCommands()) { @Override
ProxyServer.getInstance() public void onLoad(File dataFolder) {
.getPluginManager() this.dataFolder = dataFolder;
.registerCommand(getPlugin(), new BungeeCommand(command));
} }
}
@Override @Override
public void onDisable() { public void onEnable() {
AntiVPN.getInstance().stop(); pluginInstance = this;
}
private String getDatabaseType() { //Setting up config
VPNDatabase database = AntiVPN.getInstance().getDatabase(); ProxyServer.getInstance().getLogger().info("Loading config...");
if (database instanceof MySqlVPN) {
return "MySQL";
} else if (database instanceof H2VPN) { //Loading plugin
return "H2"; ProxyServer.getInstance().getLogger().info("Starting AntiVPN services...");
} else if (database instanceof MongoVPN) { AntiVPN.start(new BungeeListener(), new BungeePlayerExecutor(), getDataFolder());
return "MongoDB";
} else { if(AntiVPN.getInstance().getVpnConfig().metrics()) {
return "No-Database"; ProxyServer.getInstance().getLogger().info("Starting bStats metrics...");
Metrics metrics = new Metrics(getPlugin(), 12616);
metrics.addCustomChart(new SimplePie("database_used", this::getDatabaseType));
ProxyServer.getInstance().getScheduler().schedule(getPlugin(),
() -> AntiVPN.getInstance().checked = AntiVPN.getInstance().detections = 0,
10, 10, TimeUnit.MINUTES);
}
for (Command command : AntiVPN.getInstance().getCommands()) {
ProxyServer.getInstance().getPluginManager().registerCommand(getPlugin(), new BungeeCommand(command));
}
} }
}
public ProxyServer getProxy() { @Override
return ProxyServer.getInstance(); public void onDisable() {
} AntiVPN.getInstance().stop();
}
private String getDatabaseType() {
VPNDatabase database = AntiVPN.getInstance().getDatabase();
if(database instanceof MySqlVPN) {
return "MySQL";
} else if(database instanceof H2VPN) {
return "H2";
} else if(database instanceof MongoVPN) {
return "MongoDB";
} else {
return "No-Database";
}
}
public ProxyServer getProxy() {
return ProxyServer.getInstance();
}
} }
@@ -17,8 +17,6 @@
package dev.brighten.antivpn.bungee.command; package dev.brighten.antivpn.bungee.command;
import dev.brighten.antivpn.AntiVPN; import dev.brighten.antivpn.AntiVPN;
import java.util.Arrays;
import java.util.stream.IntStream;
import lombok.val; import lombok.val;
import net.md_5.bungee.api.ChatColor; import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.CommandSender; import net.md_5.bungee.api.CommandSender;
@@ -26,87 +24,72 @@ import net.md_5.bungee.api.chat.TextComponent;
import net.md_5.bungee.api.plugin.Command; import net.md_5.bungee.api.plugin.Command;
import net.md_5.bungee.api.plugin.TabExecutor; import net.md_5.bungee.api.plugin.TabExecutor;
import java.util.Arrays;
import java.util.stream.IntStream;
public class BungeeCommand extends Command implements TabExecutor { public class BungeeCommand extends Command implements TabExecutor {
private final dev.brighten.antivpn.command.Command command; private final dev.brighten.antivpn.command.Command command;
public BungeeCommand(dev.brighten.antivpn.command.Command command) {
super(command.name(), command.permission(), command.aliases());
public BungeeCommand(dev.brighten.antivpn.command.Command command) { this.command = command;
super(command.name(), command.permission(), command.aliases());
this.command = command;
}
@Override
public void execute(CommandSender sender, String[] args) {
if (!sender.hasPermission("antivpn.command.*") && !sender.hasPermission(command.permission())) {
sender.sendMessage(
TextComponent.fromLegacyText(
ChatColor.translateAlternateColorCodes(
'&',
AntiVPN.getInstance()
.getMessageHandler()
.getString("no-permission")
.getMessage())));
return;
} }
val children = command.children(); @Override
public void execute(CommandSender sender, String[] args) {
if (children.length > 0 && args.length > 0) { if(!sender.hasPermission("antivpn.command.*")
for (dev.brighten.antivpn.command.Command child : children) { && !sender.hasPermission(command.permission())) {
if (child.name().equalsIgnoreCase(args[0]) sender.sendMessage(TextComponent.fromLegacyText(ChatColor.translateAlternateColorCodes('&',
|| Arrays.stream(child.aliases()).anyMatch(alias -> alias.equalsIgnoreCase(args[0]))) { AntiVPN.getInstance().getMessageHandler().getString("no-permission").getMessage())));
if (!sender.hasPermission("antivpn.command.*")
&& !sender.hasPermission(child.permission())) {
sender.sendMessage(
TextComponent.fromLegacyText(
ChatColor.translateAlternateColorCodes(
'&',
AntiVPN.getInstance()
.getMessageHandler()
.getString("no-permission")
.getMessage())));
return; return;
}
sender.sendMessage(
TextComponent.fromLegacyText(
ChatColor.translateAlternateColorCodes(
'&',
child.execute(
new BungeeCommandExecutor(sender),
IntStream.range(0, args.length - 1)
.mapToObj(i -> args[i + 1])
.toArray(String[]::new)))));
return;
} }
}
val children = command.children();
if(children.length > 0 && args.length > 0) {
for (dev.brighten.antivpn.command.Command child : children) {
if(child.name().equalsIgnoreCase(args[0]) || Arrays.stream(child.aliases())
.anyMatch(alias -> alias.equalsIgnoreCase(args[0]))) {
if(!sender.hasPermission("antivpn.command.*")
&& !sender.hasPermission(child.permission())) {
sender.sendMessage(TextComponent.fromLegacyText(ChatColor.translateAlternateColorCodes('&',
AntiVPN.getInstance().getMessageHandler().getString("no-permission").getMessage())));
return;
}
sender.sendMessage(TextComponent
.fromLegacyText(ChatColor
.translateAlternateColorCodes('&',
child.execute(new BungeeCommandExecutor(sender), IntStream
.range(0, args.length - 1)
.mapToObj(i -> args[i + 1]).toArray(String[]::new)))));
return;
}
}
}
sender.sendMessage(TextComponent
.fromLegacyText(ChatColor
.translateAlternateColorCodes('&',
command.execute(new BungeeCommandExecutor(sender), args))));
} }
sender.sendMessage( @Override
TextComponent.fromLegacyText( public Iterable<String> onTabComplete(CommandSender sender, String[] args) {
ChatColor.translateAlternateColorCodes( val children = command.children();
'&', command.execute(new BungeeCommandExecutor(sender), args))));
}
@Override if(children.length > 0 && args.length > 0) {
public Iterable<String> onTabComplete(CommandSender sender, String[] args) { for (dev.brighten.antivpn.command.Command child : children) {
val children = command.children(); if(child.name().equalsIgnoreCase(args[0]) || Arrays.stream(child.aliases())
.anyMatch(alias2 -> alias2.equalsIgnoreCase(args[0]))) {
if (children.length > 0 && args.length > 0) { return child.tabComplete(new BungeeCommandExecutor(sender), "alias", IntStream
for (dev.brighten.antivpn.command.Command child : children) { .range(0, args.length - 1)
if (child.name().equalsIgnoreCase(args[0]) .mapToObj(i -> args[i + 1]).toArray(String[]::new));
|| Arrays.stream(child.aliases()) }
.anyMatch(alias2 -> alias2.equalsIgnoreCase(args[0]))) { }
return child.tabComplete(
new BungeeCommandExecutor(sender),
"alias",
IntStream.range(0, args.length - 1)
.mapToObj(i -> args[i + 1])
.toArray(String[]::new));
} }
} return command.tabComplete(new BungeeCommandExecutor(sender), "alias", args);
} }
return command.tabComplete(new BungeeCommandExecutor(sender), "alias", args);
}
} }
@@ -19,41 +19,39 @@ package dev.brighten.antivpn.bungee.command;
import dev.brighten.antivpn.AntiVPN; import dev.brighten.antivpn.AntiVPN;
import dev.brighten.antivpn.api.APIPlayer; import dev.brighten.antivpn.api.APIPlayer;
import dev.brighten.antivpn.command.CommandExecutor; import dev.brighten.antivpn.command.CommandExecutor;
import java.util.Optional;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import net.md_5.bungee.api.ChatColor; import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.CommandSender; import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.chat.TextComponent; import net.md_5.bungee.api.chat.TextComponent;
import net.md_5.bungee.api.connection.ProxiedPlayer; import net.md_5.bungee.api.connection.ProxiedPlayer;
import java.util.Optional;
@RequiredArgsConstructor @RequiredArgsConstructor
public class BungeeCommandExecutor implements CommandExecutor { public class BungeeCommandExecutor implements CommandExecutor {
private final CommandSender sender; private final CommandSender sender;
@Override @Override
public void sendMessage(String message, Object... objects) { public void sendMessage(String message, Object... objects) {
sender.sendMessage( sender.sendMessage(TextComponent.fromLegacyText(ChatColor
TextComponent.fromLegacyText( .translateAlternateColorCodes('&', String.format(message, objects))));
ChatColor.translateAlternateColorCodes('&', String.format(message, objects)))); }
}
@Override @Override
public boolean hasPermission(String permission) { public boolean hasPermission(String permission) {
return sender.hasPermission(permission); return sender.hasPermission(permission);
} }
@Override @Override
public Optional<APIPlayer> getPlayer() { public Optional<APIPlayer> getPlayer() {
if (!isPlayer()) return Optional.empty(); if(!isPlayer()) return Optional.empty();
return AntiVPN.getInstance() return AntiVPN.getInstance().getPlayerExecutor().getPlayer(((ProxiedPlayer) sender).getUniqueId());
.getPlayerExecutor() }
.getPlayer(((ProxiedPlayer) sender).getUniqueId());
}
@Override @Override
public boolean isPlayer() { public boolean isPlayer() {
return sender instanceof ProxiedPlayer; return sender instanceof ProxiedPlayer;
} }
} }
@@ -1,114 +1,110 @@
package dev.brighten.antivpn.bungee; package dev.brighten.antivpn.bungee;
import static org.mockito.Mockito.*;
import dev.brighten.antivpn.AntiVPN; import dev.brighten.antivpn.AntiVPN;
import dev.brighten.antivpn.StandardTest;
import dev.brighten.antivpn.api.PlayerExecutor; import dev.brighten.antivpn.api.PlayerExecutor;
import dev.brighten.antivpn.api.VPNConfig; import dev.brighten.antivpn.api.VPNConfig;
import dev.brighten.antivpn.api.VPNExecutor; import dev.brighten.antivpn.api.VPNExecutor;
import dev.brighten.antivpn.message.MessageHandler; import dev.brighten.antivpn.message.MessageHandler;
import dev.brighten.antivpn.message.VpnString; import dev.brighten.antivpn.message.VpnString;
import dev.brighten.antivpn.web.objects.VPNResponse; import dev.brighten.antivpn.web.objects.VPNResponse;
import java.lang.reflect.Field;
import java.net.InetSocketAddress;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import net.md_5.bungee.api.connection.PendingConnection; import net.md_5.bungee.api.connection.PendingConnection;
import net.md_5.bungee.api.event.PreLoginEvent; import net.md_5.bungee.api.event.PreLoginEvent;
import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
public class BungeeListenerTest extends StandardTest { import java.lang.reflect.Field;
import java.net.InetSocketAddress;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
private BungeeListener listener; import static org.mockito.Mockito.*;
private VPNExecutor vpnExecutor;
@BeforeEach public class BungeeListenerTest {
public void setUp() throws Exception {
AntiVPN antiVPN = mock(AntiVPN.class);
VPNConfig config = mock(VPNConfig.class);
PlayerExecutor playerExecutor = mock(PlayerExecutor.class);
vpnExecutor = mock(VPNExecutor.class);
MessageHandler messageHandler = mock(MessageHandler.class);
when(antiVPN.getVpnConfig()).thenReturn(config); private BungeeListener listener;
when(antiVPN.getPlayerExecutor()).thenReturn(playerExecutor); private VPNExecutor vpnExecutor;
when(antiVPN.getExecutor()).thenReturn(vpnExecutor);
when(antiVPN.getMessageHandler()).thenReturn(messageHandler);
when(playerExecutor.getPlayer(any(UUID.class))).thenReturn(Optional.empty()); @BeforeEach
when(config.getPrefixWhitelists()).thenReturn(java.util.Collections.emptyList()); public void setUp() throws Exception {
when(config.getCountryList()).thenReturn(java.util.Collections.emptyList()); AntiVPN antiVPN = mock(AntiVPN.class);
when(config.isKickPlayers()).thenReturn(true); VPNConfig config = mock(VPNConfig.class);
when(config.getKickMessage()).thenReturn("Blocked!"); PlayerExecutor playerExecutor = mock(PlayerExecutor.class);
vpnExecutor = mock(VPNExecutor.class);
MessageHandler messageHandler = mock(MessageHandler.class);
VpnString mockVpnString = mock(VpnString.class); when(antiVPN.getVpnConfig()).thenReturn(config);
when(mockVpnString.getFormattedMessage(any())).thenReturn("Blocked!"); when(antiVPN.getPlayerExecutor()).thenReturn(playerExecutor);
when(messageHandler.getString(anyString())).thenReturn(mockVpnString); when(antiVPN.getExecutor()).thenReturn(vpnExecutor);
when(antiVPN.getMessageHandler()).thenReturn(messageHandler);
when(vpnExecutor.checkIp(anyString())) when(playerExecutor.getPlayer(any(UUID.class))).thenReturn(Optional.empty());
.thenReturn( when(config.getPrefixWhitelists()).thenReturn(java.util.Collections.emptyList());
CompletableFuture.completedFuture( when(config.getCountryList()).thenReturn(java.util.Collections.emptyList());
VPNResponse.builder() when(config.isKickPlayers()).thenReturn(true);
.success(true) when(config.getKickMessage()).thenReturn("Blocked!");
.proxy(false)
.ip("127.0.0.1")
.method("N/A")
.countryName("N/A")
.city("N/A")
.build()));
// Use reflection to set the private static INSTANCE field VpnString mockVpnString = mock(VpnString.class);
Field instanceField = AntiVPN.class.getDeclaredField("INSTANCE"); when(mockVpnString.getFormattedMessage(any())).thenReturn("Blocked!");
instanceField.setAccessible(true); when(messageHandler.getString(anyString())).thenReturn(mockVpnString);
instanceField.set(null, antiVPN);
listener = new BungeeListener(); when(vpnExecutor.checkIp(anyString())).thenReturn(CompletableFuture.completedFuture(
} VPNResponse.builder().success(true).proxy(false).ip("127.0.0.1")
.method("N/A").countryName("N/A").city("N/A").build()
));
@AfterEach // Use reflection to set the private static INSTANCE field
public void tearDown() throws Exception { Field instanceField = AntiVPN.class.getDeclaredField("INSTANCE");
// Reset the singleton instanceField.setAccessible(true);
Field instanceField = AntiVPN.class.getDeclaredField("INSTANCE"); instanceField.set(null, antiVPN);
instanceField.setAccessible(true);
instanceField.set(null, null);
}
@Test listener = new BungeeListener();
public void testPreLoginEventAllowed() { }
PreLoginEvent event = mock(PreLoginEvent.class);
PendingConnection connection = mock(PendingConnection.class);
when(event.getConnection()).thenReturn(connection); @AfterEach
when(connection.getUniqueId()).thenReturn(UUID.randomUUID()); public void tearDown() throws Exception {
when(connection.getName()).thenReturn("TestPlayer"); // Reset the singleton
when(connection.getSocketAddress()).thenReturn(new InetSocketAddress("127.0.0.1", 12345)); Field instanceField = AntiVPN.class.getDeclaredField("INSTANCE");
instanceField.setAccessible(true);
instanceField.set(null, null);
}
listener.onListener(event); @Test
public void testPreLoginEventAllowed() {
PreLoginEvent event = mock(PreLoginEvent.class);
PendingConnection connection = mock(PendingConnection.class);
verify(event, never()).setCancelled(true); when(event.getConnection()).thenReturn(connection);
} when(connection.getUniqueId()).thenReturn(UUID.randomUUID());
when(connection.getName()).thenReturn("TestPlayer");
when(connection.getSocketAddress()).thenReturn(new InetSocketAddress("127.0.0.1", 12345));
@Test listener.onListener(event);
public void testPreLoginEventBlocked() throws NoSuchFieldException, IllegalAccessException {
PreLoginEvent event = mock(PreLoginEvent.class);
PendingConnection connection = mock(PendingConnection.class);
UUID uuid = UUID.randomUUID(); verify(event, never()).setCancelled(true);
when(event.getConnection()).thenReturn(connection); }
when(connection.getUniqueId()).thenReturn(uuid);
when(connection.getName()).thenReturn("ProxyPlayer");
when(connection.getSocketAddress()).thenReturn(new InetSocketAddress("1.1.1.1", 12345));
// Mock proxy response @Test
mockCache(); public void testPreLoginEventBlocked() {
PreLoginEvent event = mock(PreLoginEvent.class);
PendingConnection connection = mock(PendingConnection.class);
listener.onListener(event); UUID uuid = UUID.randomUUID();
when(event.getConnection()).thenReturn(connection);
when(connection.getUniqueId()).thenReturn(uuid);
when(connection.getName()).thenReturn("ProxyPlayer");
when(connection.getSocketAddress()).thenReturn(new InetSocketAddress("1.1.1.1", 12345));
verify(event).setCancelled(true); // Mock proxy response
verify(event).setReason(any()); when(vpnExecutor.checkIp("1.1.1.1")).thenReturn(CompletableFuture.completedFuture(
} VPNResponse.builder().success(true).proxy(true).ip("1.1.1.1")
.method("N/A").countryName("N/A").countryCode("N/A").city("N/A").build()
));
listener.onListener(event);
verify(event).setCancelled(true);
verify(event).setReason(any());
}
} }
-22
View File
@@ -4,28 +4,6 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [1.10.1.2] 2026-05-03
### Fixed
- Kick spam on non-cached VPN detection events.
### Changed
- Project cleanup and performance improvements.
## [1.10.1.1] 2026-04-29
### Fixed
- Startup error on velocity instances is now corrected.
### Changed
- Bumped dependency versions for H2 database.
## [1.10.1] - 2026-04-28
### Fixed
- Async kick error that caused issues during player removal when running commands.
- Startup error caused by a packaging issue is now corrected.
## [1.10.0] - 2026-04-07 ## [1.10.0] - 2026-04-07
### Added ### Added
+7 -4
View File
@@ -1,6 +1,5 @@
plugins { plugins {
id 'com.gradleup.shadow' id 'com.gradleup.shadow'
id 'java-test-fixtures'
} }
dependencies { dependencies {
@@ -10,7 +9,7 @@ dependencies {
implementation 'org.jetbrains:annotations:26.0.2' implementation 'org.jetbrains:annotations:26.0.2'
compileOnly 'com.mysql:mysql-connector-j:9.3.0' compileOnly 'com.mysql:mysql-connector-j:9.3.0'
compileOnly 'com.h2database:h2:2.4.240' compileOnly 'com.h2database:h2:2.2.220'
implementation'com.github.ben-manes.caffeine:caffeine:3.1.8' implementation'com.github.ben-manes.caffeine:caffeine:3.1.8'
compileOnly 'org.mongodb:mongo-java-driver:3.12.14' compileOnly 'org.mongodb:mongo-java-driver:3.12.14'
@@ -22,14 +21,18 @@ dependencies {
testImplementation 'org.testcontainers:mongodb:1.20.4' testImplementation 'org.testcontainers:mongodb:1.20.4'
testRuntimeOnly 'org.slf4j:slf4j-simple:2.0.16' testRuntimeOnly 'org.slf4j:slf4j-simple:2.0.16'
testImplementation 'com.mysql:mysql-connector-j:9.3.0' testImplementation 'com.mysql:mysql-connector-j:9.3.0'
testImplementation 'com.h2database:h2:2.4.240' testImplementation 'com.h2database:h2:2.2.220'
testImplementation 'org.mongodb:mongo-java-driver:3.12.14' testImplementation 'org.mongodb:mongo-java-driver:3.12.14'
testImplementation 'com.github.ben-manes.caffeine:caffeine:3.1.8' testImplementation 'com.github.ben-manes.caffeine:caffeine:3.1.8'
testFixturesImplementation 'com.github.ben-manes.caffeine:caffeine:3.1.8'
} }
shadowJar { shadowJar {
archiveClassifier.set('') archiveClassifier.set('')
dependencies {
exclude 'dev/brighten/antivpn/depends/Relocate*'
exclude 'dev/brighten/antivpn/depends/MavenLibraries*'
}
} }
tasks.build.dependsOn shadowJar tasks.build.dependsOn shadowJar
@@ -33,6 +33,10 @@ import dev.brighten.antivpn.utils.MiscUtils;
import dev.brighten.antivpn.utils.config.Configuration; import dev.brighten.antivpn.utils.config.Configuration;
import dev.brighten.antivpn.utils.config.ConfigurationProvider; import dev.brighten.antivpn.utils.config.ConfigurationProvider;
import dev.brighten.antivpn.utils.config.YamlConfiguration; import dev.brighten.antivpn.utils.config.YamlConfiguration;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.Setter;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
@@ -40,253 +44,223 @@ import java.net.URL;
import java.net.URLConnection; import java.net.URLConnection;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.Setter;
@Getter @Getter
@Setter(AccessLevel.PRIVATE) @Setter(AccessLevel.PRIVATE)
@MavenLibrary(groupId = "com.h2database", artifactId = "h2", version = "2.4.240") @MavenLibrary(groupId = "com.h2database", artifactId ="h2", version = "2.2.220")
@MavenLibrary(groupId = "org.mongodb", artifactId = "mongo-java-driver", version = "3.12.14") @MavenLibrary(groupId = "org.mongodb", artifactId = "mongo-java-driver", version = "3.12.14")
@MavenLibrary(groupId = "com.mysql", artifactId = "mysql-connector-j", version = "9.3.0") @MavenLibrary(
groupId = "com.mysql",
artifactId = "mysql-connector-j",
version = "9.3.0"
)
public class AntiVPN { public class AntiVPN {
private static AntiVPN INSTANCE; private static AntiVPN INSTANCE;
private VPNConfig vpnConfig; private VPNConfig vpnConfig;
private VPNExecutor executor; private VPNExecutor executor;
private PlayerExecutor playerExecutor; private PlayerExecutor playerExecutor;
private VPNDatabase database; private VPNDatabase database;
private MessageHandler messageHandler; private MessageHandler messageHandler;
private Configuration config; private Configuration config;
private List<Command> commands = new ArrayList<>(); private List<Command> commands = new ArrayList<>();
public int detections, checked; public int detections, checked;
private File pluginFolder; private File pluginFolder;
public static void start(VPNExecutor executor, PlayerExecutor playerExecutor, File pluginFolder) { public static void start(VPNExecutor executor, PlayerExecutor playerExecutor, File pluginFolder) {
// Initializing //Initializing
INSTANCE = new AntiVPN(); INSTANCE = new AntiVPN();
INSTANCE.pluginFolder = pluginFolder; INSTANCE.pluginFolder = pluginFolder;
INSTANCE.executor = executor; INSTANCE.executor = executor;
INSTANCE.playerExecutor = playerExecutor; INSTANCE.playerExecutor = playerExecutor;
LibraryLoader.loadAll(INSTANCE); LibraryLoader.loadAll(INSTANCE);
try { try {
File configFile = new File(pluginFolder, "config.yml"); File configFile = new File(pluginFolder, "config.yml");
if (!configFile.exists()) { if(!configFile.exists()){
if (configFile.getParentFile().mkdirs()) { if(configFile.getParentFile().mkdirs()) {
AntiVPN.getInstance().getExecutor().log("Created plugin folder!"); AntiVPN.getInstance().getExecutor().log("Created plugin folder!");
}
MiscUtils.copy(INSTANCE.getResource( "config.yml"), configFile);
}
INSTANCE.config = ConfigurationProvider.getProvider(YamlConfiguration.class)
.load(configFile);
} catch (IOException e) {
AntiVPN.getInstance().getExecutor().logException("Could not load config.yml, plugin disabling...", e);
executor.disablePlugin();
return;
} }
MiscUtils.copy(INSTANCE.getResource("config.yml"), configFile);
}
INSTANCE.config = ConfigurationProvider.getProvider(YamlConfiguration.class).load(configFile);
} catch (IOException e) {
AntiVPN.getInstance()
.getExecutor()
.logException("Could not load config.yml, plugin disabling...", e);
executor.disablePlugin();
return;
}
INSTANCE.vpnConfig = new VPNConfig(); INSTANCE.vpnConfig = new VPNConfig();
INSTANCE.executor.registerListeners(); INSTANCE.executor.registerListeners();
INSTANCE.vpnConfig.update(); INSTANCE.vpnConfig.update();
INSTANCE.messageHandler = new MessageHandler(); INSTANCE.messageHandler = new MessageHandler();
try { try {
switch (INSTANCE.vpnConfig.getDatabaseType().toLowerCase()) { switch(INSTANCE.vpnConfig.getDatabaseType().toLowerCase()) {
case "h2": case "h2":
case "local": case "local":
case "flatfile": case "flatfile": {
{ AntiVPN.getInstance().getExecutor().log("Using databaseType H2...");
AntiVPN.getInstance().getExecutor().log("Using databaseType H2..."); INSTANCE.database = new H2VPN();
INSTANCE.database = new H2VPN(); INSTANCE.database.init();
INSTANCE.database.init(); break;
break; }
} case "mysql":
case "mysql": case "sql": {
case "sql": AntiVPN.getInstance().getExecutor().log("Using databaseType MySQL...");
{ INSTANCE.database = new MySqlVPN();
AntiVPN.getInstance().getExecutor().log("Using databaseType MySQL..."); INSTANCE.database.init();
INSTANCE.database = new MySqlVPN(); break;
INSTANCE.database.init(); }
break; case "mongo":
} case "mongodb":
case "mongo": case "mongod": {
case "mongodb": INSTANCE.database = new MongoVPN();
case "mongod": INSTANCE.database.init();
{ break;
INSTANCE.database = new MongoVPN(); }
INSTANCE.database.init(); default: {
break; AntiVPN.getInstance().getExecutor().log("Could not find database type \"" + INSTANCE.vpnConfig.getDatabaseType() + "\". " +
} "Options: [MySQL]");
default: break;
{ }
AntiVPN.getInstance() }
.getExecutor() } catch (Exception e) {
.log( AntiVPN.getInstance().getExecutor().logException("Could not initialize database, plugin disabling...", e);
"Could not find database type \"" executor.disablePlugin();
+ INSTANCE.vpnConfig.getDatabaseType() return;
+ "\". " }
+ "Options: [MySQL]");
break;
}
}
} catch (Exception e) {
AntiVPN.getInstance()
.getExecutor()
.logException("Could not initialize database, plugin disabling...", e);
executor.disablePlugin();
return;
}
// Registering commands //Registering commands
INSTANCE.registerCommands(); INSTANCE.registerCommands();
// Turning on alerts of players who are already online. //Turning on alerts of players who are already online.
playerExecutor playerExecutor.getOnlinePlayers().forEach(player -> {
.getOnlinePlayers() //We want to make sure they even have permission to see alerts before we make a bunch
.forEach( //of unnecessary database queries.
player -> { if(player.hasPermission("antivpn.command.alerts")) {
// We want to make sure they even have permission to see alerts before we make a bunch //Running database check for enabled alerts.
// of unnecessary database queries.
if (player.hasPermission("antivpn.command.alerts")) {
// Running database check for enabled alerts.
INSTANCE.database.alertsState(player.getUuid(), player::setAlertsEnabled); INSTANCE.database.alertsState(player.getUuid(), player::setAlertsEnabled);
} }
}); });
AntiVPN.getInstance() AntiVPN.getInstance().getMessageHandler().initStrings(vpnString -> new ConfigDefault<>
.getMessageHandler() (vpnString.getDefaultMessage(), "messages." + vpnString.getKey(), AntiVPN.getInstance())
.initStrings( .get());
vpnString -> AntiVPN.getInstance().getMessageHandler().reloadStrings();
new ConfigDefault<>(
vpnString.getDefaultMessage(),
"messages." + vpnString.getKey(),
AntiVPN.getInstance())
.get());
AntiVPN.getInstance().getMessageHandler().reloadStrings();
// Starting kick checks // Starting kick checks
AntiVPN.getInstance().getExecutor().startKickChecks(); AntiVPN.getInstance().getExecutor().startKickChecks();
} }
public InputStream getResource(String filename) { public InputStream getResource(String filename) {
if (filename == null) { if (filename == null) {
throw new IllegalArgumentException("Filename cannot be null"); throw new IllegalArgumentException("Filename cannot be null");
} else {
try {
URL url = executor.getClass().getClassLoader().getResource(filename);
if (url == null) {
return null;
} else { } else {
URLConnection connection = url.openConnection(); try {
connection.setUseCaches(false); URL url = executor.getClass().getClassLoader().getResource(filename);
return connection.getInputStream(); if (url == null) {
} return null;
} catch (IOException var4) { } else {
return null; URLConnection connection = url.openConnection();
} connection.setUseCaches(false);
} return connection.getInputStream();
} }
} catch (IOException var4) {
public void stop() { return null;
if (database instanceof H2VPN) { }
database.shutdown();
// Try to deregister driver
try {
java.sql.Driver driver = java.sql.DriverManager.getDriver("jdbc:h2:");
if (driver != null) {
java.sql.DriverManager.deregisterDriver(driver);
}
} catch (Exception e) {
// Log but don't throw
executor.log("Failed to deregister H2 driver: " + e.getMessage());
}
}
if (executor != null && executor.getThreadExecutor() != null) {
executor.getThreadExecutor().shutdown();
}
if (database != null) database.shutdown();
INSTANCE = null;
}
public void reloadDatabase() {
database.shutdown();
switch (AntiVPN.getInstance().getVpnConfig().getDatabaseType().toLowerCase()) {
case "h2":
case "local":
case "flatfile":
{
AntiVPN.getInstance().getExecutor().log("Using databaseType H2...");
INSTANCE.database = new H2VPN();
INSTANCE.database.init();
break;
}
case "mysql":
case "sql":
{
AntiVPN.getInstance().getExecutor().log("Using databaseType MySQL...");
INSTANCE.database = new MySqlVPN();
INSTANCE.database.init();
break;
}
case "mongo":
case "mongodb":
case "mongod":
{
INSTANCE.database = new MongoVPN();
INSTANCE.database.init();
break;
}
default:
{
AntiVPN.getInstance()
.getExecutor()
.log(
"Could not find database type \""
+ INSTANCE.vpnConfig.getDatabaseType()
+ "\". "
+ "Options: [MySQL]");
break;
} }
} }
}
public static AntiVPN getInstance() { public void stop() {
assert INSTANCE != null : "AntiVPN has not been initialized!"; if (database instanceof H2VPN) {
database.shutdown();
return INSTANCE; // Try to deregister driver
} try {
java.sql.Driver driver = java.sql.DriverManager.getDriver("jdbc:h2:");
if (driver != null) {
java.sql.DriverManager.deregisterDriver(driver);
}
} catch (Exception e) {
// Log but don't throw
executor.log("Failed to deregister H2 driver: " + e.getMessage());
}
}
if (executor != null && executor.getThreadExecutor() != null) {
executor.getThreadExecutor().shutdown();
}
if(database != null) database.shutdown();
public void saveConfig() { INSTANCE = null;
try {
ConfigurationProvider.getProvider(YamlConfiguration.class)
.save(getConfig(), new File(pluginFolder.getPath() + File.separator + "config.yml"));
} catch (IOException e) {
AntiVPN.getInstance().getExecutor().logException(e);
} }
}
public void reloadConfig() { public void reloadDatabase() {
try { database.shutdown();
config = switch(AntiVPN.getInstance().getVpnConfig().getDatabaseType().toLowerCase()) {
ConfigurationProvider.getProvider(YamlConfiguration.class) case "h2":
.load(new File(pluginFolder.getPath() + File.separator + "config.yml")); case "local":
} catch (IOException e) { case "flatfile": {
throw new RuntimeException(e); AntiVPN.getInstance().getExecutor().log("Using databaseType H2...");
INSTANCE.database = new H2VPN();
INSTANCE.database.init();
break;
}
case "mysql":
case "sql":{
AntiVPN.getInstance().getExecutor().log("Using databaseType MySQL...");
INSTANCE.database = new MySqlVPN();
INSTANCE.database.init();
break;
}
case "mongo":
case "mongodb":
case "mongod": {
INSTANCE.database = new MongoVPN();
INSTANCE.database.init();
break;
}
default: {
AntiVPN.getInstance().getExecutor().log("Could not find database type \"" + INSTANCE.vpnConfig.getDatabaseType() + "\". " +
"Options: [MySQL]");
break;
}
}
} }
}
private void registerCommands() { public static AntiVPN getInstance() {
commands.add(new AntiVPNCommand()); assert INSTANCE != null: "AntiVPN has not been initialized!";
}
return INSTANCE;
}
public void saveConfig() {
try {
ConfigurationProvider.getProvider(YamlConfiguration.class)
.save(getConfig(), new File(pluginFolder.getPath() + File.separator + "config.yml"));
} catch (IOException e) {
AntiVPN.getInstance().getExecutor().logException(e);
}
}
public void reloadConfig() {
try {
config = ConfigurationProvider.getProvider(YamlConfiguration.class)
.load(new File(pluginFolder.getPath() + File.separator + "config.yml"));
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private void registerCommands() {
commands.add(new AntiVPNCommand());
}
} }
@@ -20,152 +20,126 @@ import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine; import com.github.benmanes.caffeine.cache.Caffeine;
import dev.brighten.antivpn.AntiVPN; import dev.brighten.antivpn.AntiVPN;
import dev.brighten.antivpn.message.VpnString; import dev.brighten.antivpn.message.VpnString;
import java.net.InetAddress;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import java.net.InetAddress;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.logging.Level;
@Getter @Getter
public abstract class APIPlayer { public abstract class APIPlayer {
private final UUID uuid; private final UUID uuid;
private final String name; private final String name;
private final InetAddress ip; private final InetAddress ip;
@Setter private boolean alertsEnabled; @Setter
private boolean alertsEnabled;
public static final Cache<String, CheckResult> checkResultCache = private static final Cache<String, CheckResult> checkResultCache = Caffeine.newBuilder()
Caffeine.newBuilder().expireAfterWrite(5, TimeUnit.MINUTES).maximumSize(2000).build(); .expireAfterWrite(5, TimeUnit.MINUTES)
.maximumSize(2000)
.build();
public APIPlayer(UUID uuid, String name, InetAddress ip) { public APIPlayer(UUID uuid, String name, InetAddress ip) {
this.uuid = uuid; this.uuid = uuid;
this.name = name; this.name = name;
this.ip = ip; this.ip = ip;
}
public abstract void sendMessage(String message);
public abstract void kickPlayer(String reason);
public abstract boolean hasPermission(String permission);
public void updateAlertsState() {
// Updating into database so its synced across servers and saved on logout.
AntiVPN.getInstance().getDatabase().updateAlertsState(uuid, alertsEnabled);
sendMessage(
AntiVPN.getInstance()
.getMessageHandler()
.getString("command-alerts-toggled")
.getFormattedMessage(new VpnString.Var<>("state", alertsEnabled)));
}
public void checkAlertsState() {
AntiVPN.getInstance()
.getExecutor()
.getThreadExecutor()
.execute(
() ->
AntiVPN.getInstance()
.getDatabase()
.alertsState(
uuid,
state -> {
if (state) {
alertsEnabled = true;
updateAlertsState();
}
}));
}
/***
* The result is only returned if it is cached. Otherwise, there is an asynchronous call to the API
* and will handle kicking the player if necessary.
* @return CheckResult - The cached result of the check if it exists.
*/
public CheckResult checkPlayer() {
if (hasPermission("antivpn.bypass") // Has bypass permission
// Is exempt
|| (uuid != null && AntiVPN.getInstance().getExecutor().isWhitelisted(uuid))
// Or has a name that starts with a certain prefix. This is for Bedrock exempting.
|| AntiVPN.getInstance().getExecutor().isWhitelisted(ip.getHostAddress() + "/32")
|| AntiVPN.getInstance().getVpnConfig().getPrefixWhitelists().stream()
.anyMatch(name::startsWith)) {
return new CheckResult(null, ResultType.WHITELISTED, false);
} }
CheckResult cachedResult = checkResultCache.getIfPresent(ip.getHostAddress()); public abstract void sendMessage(String message);
if (cachedResult != null) { public abstract void kickPlayer(String reason);
if (cachedResult.response().getIp().equals(ip.getHostAddress())) {
AntiVPN.getInstance() public abstract boolean hasPermission(String permission);
.getExecutor()
.log( public void updateAlertsState() {
Level.FINE, //Updating into database so its synced across servers and saved on logout.
"Cached result for " + ip.getHostAddress() + " is " + cachedResult.resultType()); AntiVPN.getInstance().getDatabase().updateAlertsState(uuid, alertsEnabled);
if (cachedResult.resultType().isShouldBlock()) {
AntiVPN.getInstance().getExecutor().handleKickingOfPlayer(cachedResult, this); sendMessage(AntiVPN.getInstance().getMessageHandler()
.getString("command-alerts-toggled")
.getFormattedMessage(new VpnString.Var<>("state", alertsEnabled)));
}
public void checkAlertsState() {
AntiVPN.getInstance().getExecutor().getThreadExecutor().execute(() ->
AntiVPN.getInstance().getDatabase().alertsState(uuid, state -> {
if(state) {
alertsEnabled = true;
updateAlertsState();
}
})
);
}
public void checkPlayer(Consumer<CheckResult> onResult) {
if (hasPermission("antivpn.bypass") //Has bypass permission
//Is exempt
|| (uuid != null && AntiVPN.getInstance().getExecutor().isWhitelisted(uuid))
//Or has a name that starts with a certain prefix. This is for Bedrock exempting.
|| AntiVPN.getInstance().getExecutor().isWhitelisted(ip.getHostAddress() + "/32")
|| AntiVPN.getInstance().getVpnConfig().getPrefixWhitelists().stream()
.anyMatch(name::startsWith)) {
onResult.accept(new CheckResult(null, ResultType.WHITELISTED, false));
return;
} }
return cachedResult;
}
}
AntiVPN.getInstance() CheckResult cachedResult = checkResultCache.getIfPresent(ip.getHostAddress());
.getExecutor()
.checkIp(ip.getHostAddress()) if(cachedResult != null) {
.thenAccept( if(cachedResult.response().getIp().equals(ip.getHostAddress())) {
result -> { AntiVPN.getInstance().getExecutor().log(Level.FINE, "Cached result for " + ip.getHostAddress() + " is " + cachedResult.resultType());
if (!result.isSuccess()) { if(cachedResult.resultType().isShouldBlock()) {
AntiVPN.getInstance() AntiVPN.getInstance().getExecutor().handleKickingOfPlayer(cachedResult, this);
.getExecutor() }
.log( onResult.accept(cachedResult);
Level.WARNING,
"The API query was not a success! "
+ "You may need to upgrade your license on "
+ "https://funkemunky.cc/shop");
return; return;
} }
// If the countryList() size is zero, no need to check. }
// Running country check first
CheckResult checkResult;
if (!AntiVPN.getInstance().getVpnConfig().getCountryList().isEmpty()
&& !((uuid != null && AntiVPN.getInstance().getExecutor().isWhitelisted(uuid))
// Or has a name that starts with a certain prefix. This is for Bedrock
// exempting.
|| AntiVPN.getInstance()
.getExecutor()
.isWhitelisted(ip.getHostAddress() + "/32"))
// This bit of code will decide whether or not to kick the player
// If it contains the code and it is set to whitelist, it will not kick
// as they are equal and vise versa. However, if the contains does not match
// the state, it will kick.
&& AntiVPN.getInstance()
.getVpnConfig()
.getCountryList()
.contains(result.getCountryCode())
!= AntiVPN.getInstance().getVpnConfig().getWhitelistCountries()) {
// Using our built in kicking system if no commands are configured
checkResult = new CheckResult(result, ResultType.DENIED_COUNTRY, false);
} else if (result.isProxy()) {
checkResult = new CheckResult(result, ResultType.DENIED_PROXY, false);
} else {
checkResult = new CheckResult(result, ResultType.ALLOWED, false);
}
AntiVPN.getInstance() AntiVPN.getInstance().getExecutor().checkIp(ip.getHostAddress())
.getExecutor() .thenAccept(result -> {
.log( if(!result.isSuccess()) {
Level.FINE, AntiVPN.getInstance().getExecutor().log(Level.WARNING, "The API query was not a success! " +
"Result for " + ip.getHostAddress() + " is " + checkResult.resultType()); "You may need to upgrade your license on " +
"https://funkemunky.cc/shop");
onResult.accept(new CheckResult(null, ResultType.API_FAILURE, false));
return;
}
// If the countryList() size is zero, no need to check.
// Running country check first
CheckResult checkResult;
if (!AntiVPN.getInstance().getVpnConfig().getCountryList().isEmpty()
&& !((uuid != null && AntiVPN.getInstance().getExecutor()
.isWhitelisted(uuid))
//Or has a name that starts with a certain prefix. This is for Bedrock exempting.
|| AntiVPN.getInstance().getExecutor().isWhitelisted(ip.getHostAddress() + "/32"))
// This bit of code will decide whether or not to kick the player
// If it contains the code and it is set to whitelist, it will not kick
// as they are equal and vise versa. However, if the contains does not match
// the state, it will kick.
&& AntiVPN.getInstance().getVpnConfig().getCountryList()
.contains(result.getCountryCode())
!= AntiVPN.getInstance().getVpnConfig().getWhitelistCountries()) {
//Using our built in kicking system if no commands are configured
checkResult = new CheckResult(result, ResultType.DENIED_COUNTRY, false);
} else if (result.isProxy()) {
checkResult = new CheckResult(result, ResultType.DENIED_PROXY, false);
} else {
checkResult = new CheckResult(result, ResultType.ALLOWED, false);
}
checkResultCache.put( AntiVPN.getInstance().getExecutor().log(Level.FINE, "Result for " + ip.getHostAddress() + " is " + checkResult.resultType());
ip.getHostAddress(),
new CheckResult(checkResult.response(), checkResult.resultType(), true)); checkResultCache.put(ip.getHostAddress(), new CheckResult(checkResult.response(), checkResult.resultType(), true));
if (checkResult.resultType().isShouldBlock()) { if(checkResult.resultType().isShouldBlock()) {
AntiVPN.getInstance().getExecutor().handleKickingOfPlayer(checkResult, this); AntiVPN.getInstance().getExecutor().handleKickingOfPlayer(checkResult, this);
} }
AntiVPN.getInstance().checked++; onResult.accept(checkResult);
}); AntiVPN.getInstance().checked++;
return new CheckResult(null, ResultType.UNKNOWN, false); });
} onResult.accept(new CheckResult(null, ResultType.UNKNOWN, false));
}
} }
@@ -18,4 +18,5 @@ package dev.brighten.antivpn.api;
import dev.brighten.antivpn.web.objects.VPNResponse; import dev.brighten.antivpn.web.objects.VPNResponse;
public record CheckResult(VPNResponse response, ResultType resultType, boolean isFromCache) {} public record CheckResult(VPNResponse response, ResultType resultType, boolean isFromCache) {
}
@@ -21,18 +21,22 @@ import java.util.UUID;
public class OfflinePlayer extends APIPlayer { public class OfflinePlayer extends APIPlayer {
public OfflinePlayer(UUID uuid, String name, InetAddress ip) { public OfflinePlayer(UUID uuid, String name, InetAddress ip) {
super(uuid, name, ip); super(uuid, name, ip);
} }
@Override @Override
public void sendMessage(String message) {} public void sendMessage(String message) {
@Override }
public void kickPlayer(String reason) {}
@Override @Override
public boolean hasPermission(String permission) { public void kickPlayer(String reason) {
return false;
} }
@Override
public boolean hasPermission(String permission) {
return false;
}
} }
@@ -22,11 +22,11 @@ import java.util.UUID;
public interface PlayerExecutor { public interface PlayerExecutor {
Optional<APIPlayer> getPlayer(String name); Optional<APIPlayer> getPlayer(String name);
Optional<APIPlayer> getPlayer(UUID uuid); Optional<APIPlayer> getPlayer(UUID uuid);
void unloadPlayer(UUID uuid); void unloadPlayer(UUID uuid);
List<APIPlayer> getOnlinePlayers(); List<APIPlayer> getOnlinePlayers();
} }
@@ -19,16 +19,17 @@ package dev.brighten.antivpn.api;
import lombok.Getter; import lombok.Getter;
public enum ResultType { public enum ResultType {
ALLOWED(false), ALLOWED(false),
WHITELISTED(false), WHITELISTED(false),
DENIED_COUNTRY(true), DENIED_COUNTRY(true),
DENIED_PROXY(true), DENIED_PROXY(true),
API_FAILURE(false), API_FAILURE(false),
UNKNOWN(false); UNKNOWN(false);
@Getter private final boolean shouldBlock; @Getter
private final boolean shouldBlock;
ResultType(boolean shouldBlock) { ResultType(boolean shouldBlock) {
this.shouldBlock = shouldBlock; this.shouldBlock = shouldBlock;
} }
} }
@@ -18,203 +18,208 @@ package dev.brighten.antivpn.api;
import dev.brighten.antivpn.AntiVPN; import dev.brighten.antivpn.AntiVPN;
import dev.brighten.antivpn.utils.ConfigDefault; import dev.brighten.antivpn.utils.ConfigDefault;
import lombok.Getter;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import lombok.Getter;
public class VPNConfig { public class VPNConfig {
private final ConfigDefault<String> private final ConfigDefault<String> licenseDefault = new ConfigDefault<>("",
licenseDefault = new ConfigDefault<>("", "license", AntiVPN.getInstance()), "license", AntiVPN.getInstance()), kickStringDefault =
kickStringDefault = new ConfigDefault<>("Proxies are not allowed on our server",
new ConfigDefault<>( "kickMessage", AntiVPN.getInstance()),
"Proxies are not allowed on our server", "kickMessage", AntiVPN.getInstance()), defaultDatabaseType = new ConfigDefault<>("H2",
defaultDatabaseType = new ConfigDefault<>("H2", "database.type", AntiVPN.getInstance()), "database.type", AntiVPN.getInstance()),
defaultDatabaseName = defaultDatabaseName = new ConfigDefault<>("kaurivpn",
new ConfigDefault<>("kaurivpn", "database.database", AntiVPN.getInstance()), "database.database", AntiVPN.getInstance()),
defaultMongoURL = new ConfigDefault<>("", "database.mongoURL", AntiVPN.getInstance()), defaultMongoURL = new ConfigDefault<>("", "database.mongoURL", AntiVPN.getInstance()),
defaultUsername = new ConfigDefault<>("root", "database.username", AntiVPN.getInstance()), defaultUsername = new ConfigDefault<>("root",
defaultPassword = new ConfigDefault<>("password", "database.password", AntiVPN.getInstance()), "database.username", AntiVPN.getInstance()),
defaultCountryKickReason = defaultPassword = new ConfigDefault<>("password",
new ConfigDefault<>( "database.password", AntiVPN.getInstance()),
"&cSorry, but our server does not allow connections from\n&f%country%", defaultCountryKickReason = new ConfigDefault<>(
"countries.vanillaKickReason", AntiVPN.getInstance()), "&cSorry, but our server does not allow connections from\n&f%country%",
defaultIp = new ConfigDefault<>("localhost", "database.ip", AntiVPN.getInstance()), "countries.vanillaKickReason", AntiVPN.getInstance()),
defaultAlertMsg = defaultIp = new ConfigDefault<>("localhost", "database.ip", AntiVPN.getInstance()),
new ConfigDefault<>( defaultAlertMsg = new ConfigDefault<>("&8[&6KauriVPN&8] &e%player% &7has joined on a VPN/proxy" +
"&8[&6KauriVPN&8] &e%player% &7has joined on a VPN/proxy" " &8(&f%reason%&8) &7in location &8(&f%city%&7, &f%country%&8)", "alerts.message",
+ " &8(&f%reason%&8) &7in location &8(&f%city%&7, &f%country%&8)", AntiVPN.getInstance());
"alerts.message", AntiVPN.getInstance()); private final ConfigDefault<Boolean> cacheResultsDefault = new ConfigDefault<>(true,
private final ConfigDefault<Boolean> "cachedResults", AntiVPN.getInstance()),
cacheResultsDefault = new ConfigDefault<>(true, "cachedResults", AntiVPN.getInstance()), defaultUseCredentials = new ConfigDefault<>(true,
defaultUseCredentials = "database.useCredentials", AntiVPN.getInstance()),
new ConfigDefault<>(true, "database.useCredentials", AntiVPN.getInstance()), defaultDatabaseEnabled = new ConfigDefault<>(false, "database.enabled",
defaultDatabaseEnabled = AntiVPN.getInstance()), defaultCommandsEnable = new ConfigDefault<>(false,
new ConfigDefault<>(false, "database.enabled", AntiVPN.getInstance()), "commands.enabled", AntiVPN.getInstance()), defaultKickPlayers
defaultCommandsEnable = new ConfigDefault<>(false, "commands.enabled", AntiVPN.getInstance()), = new ConfigDefault<>(true, "kickPlayers", AntiVPN.getInstance()),
defaultKickPlayers = new ConfigDefault<>(true, "kickPlayers", AntiVPN.getInstance()), defaultAlertToStaff = new ConfigDefault<>(true, "alerts.enabled",
defaultAlertToStaff = new ConfigDefault<>(true, "alerts.enabled", AntiVPN.getInstance()), AntiVPN.getInstance()),
defaultWhitelistCountries = defaultWhitelistCountries = new ConfigDefault<>(true, "countries.whitelist",
new ConfigDefault<>(true, "countries.whitelist", AntiVPN.getInstance()), AntiVPN.getInstance()),
defaultMetrics = new ConfigDefault<>(true, "bstats", AntiVPN.getInstance()); defaultMetrics = new ConfigDefault<>(true, "bstats", AntiVPN.getInstance());
private final ConfigDefault<Integer> defaultPort = private final ConfigDefault<Integer>
new ConfigDefault<>(-1, "database.port", AntiVPN.getInstance()); defaultPort = new ConfigDefault<>(-1, "database.port", AntiVPN.getInstance());
private final ConfigDefault<List<String>> private final ConfigDefault<List<String>> prefixWhitelistsDefault = new ConfigDefault<>(new ArrayList<>(),
prefixWhitelistsDefault = "prefixWhitelists", AntiVPN.getInstance()), defaultCommands = new ConfigDefault<>(
new ConfigDefault<>(new ArrayList<>(), "prefixWhitelists", AntiVPN.getInstance()), Collections.singletonList("kick %player% VPNs are not allowed on our server!"), "commands.execute",
defaultCommands = AntiVPN.getInstance()),
new ConfigDefault<>( defCountryKickCommands = new ConfigDefault<>(Collections.emptyList(),
Collections.singletonList("kick %player% VPNs are not allowed on our server!"), "countries.commands", AntiVPN.getInstance()),
"commands.execute", defCountrylist = new ConfigDefault<>(new ArrayList<>(), "countries.list",
AntiVPN.getInstance()), AntiVPN.getInstance());
defCountryKickCommands =
new ConfigDefault<>(Collections.emptyList(), "countries.commands", AntiVPN.getInstance()),
defCountrylist =
new ConfigDefault<>(new ArrayList<>(), "countries.list", AntiVPN.getInstance());
@Getter private String license; @Getter
@Getter private String kickMessage; private String license;
@Getter private String databaseType; @Getter
@Getter private String databaseName; private String kickMessage;
private String mongoURL; @Getter
@Getter private String username; private String databaseType;
@Getter private String password; @Getter
@Getter private String ip; private String databaseName;
@Getter private String alertMsg; private String mongoURL;
@Getter private String countryVanillaKickReason; @Getter
@Getter private List<String> prefixWhitelists; private String username;
private List<String> commands; @Getter
@Getter private List<String> countryList; private String password;
private List<String> countryKickCommands; @Getter
private int port; private String ip;
private boolean cacheResults; @Getter
@Getter private boolean databaseEnabled; private String alertMsg;
private boolean useCredentials; @Getter
@Getter private boolean commandsEnabled; private String countryVanillaKickReason;
@Getter private boolean kickPlayers; @Getter
private boolean alertToStaff; private List<String> prefixWhitelists;
private boolean metrics; private List<String> commands;
private boolean whitelistCountries; @Getter
private List<String> countryList;
private List<String> countryKickCommands;
private int port;
private boolean cacheResults;
@Getter
private boolean databaseEnabled;
private boolean useCredentials;
@Getter
private boolean commandsEnabled;
@Getter
private boolean kickPlayers;
private boolean alertToStaff;
private boolean metrics;
private boolean whitelistCountries;
/** /**
* If true, results will be cached to reduce queries to <a href="https://funkemunky.cc">...</a> * If true, results will be cached to reduce queries to <a href="https://funkemunky.cc">...</a>
* * @return boolean
* @return boolean */
*/ public boolean cachedResults() {
public boolean cachedResults() { return cacheResults;
return cacheResults;
}
/**
* If true, staff will be alerted on proxy detection.
*
* @return boolean
*/
public boolean isAlertToSTaff() {
return alertToStaff;
}
/**
* Commands to run on proxy detection.
*
* @return List
*/
public List<String> commands() {
return commands;
}
/**
* Whether or not the database we want to connect to requires credentials.
*
* @return boolean
*/
public boolean useDatabaseCreds() {
return useCredentials;
}
/**
* Only for Mongo only. URL used for connecting to database. Overrides other fields
*
* @return String
*/
public String mongoDatabaseURL() {
return mongoURL;
}
/**
* If true, we only allow the {@link VPNConfig#countryKickCommands()}. If false, we blacklist
* them.
*
* @return boolean
*/
public boolean getWhitelistCountries() {
return whitelistCountries;
}
/**
* Returns our configured commands to run on player country detection.
*
* @return List
*/
public List<String> countryKickCommands() {
return countryKickCommands;
}
/**
* Gets the port based on configuration. If {@link VPNConfig#port} is -1, will get default port
* based on {@link VPNConfig#getDatabaseType()} lowerCase().
*
* @return int
*/
public int getPort() {
if (port == -1) {
switch (getDatabaseType().toLowerCase()) {
case "mongodb":
case "mongo":
case "mongod":
return 27017;
case "sql":
case "mysql":
return 3306;
}
} }
return port; /**
} * If true, staff will be alerted on proxy detection.
* @return boolean
*/
public boolean isAlertToSTaff() {
return alertToStaff;
}
/** /**
* If true, <a href="https://bstats.org">...</a> metrics will be collected to improve KauriVPN. * Commands to run on proxy detection.
* * @return List
* @return boolean */
*/ public List<String> commands() {
public boolean metrics() { return commands;
return metrics; }
}
/**
* Whether or not the database we want to connect to requires credentials.
* @return boolean
*/
public boolean useDatabaseCreds() {
return useCredentials;
}
/**
* Only for Mongo only. URL used for connecting to database. Overrides other fields
* @return String
*/
public String mongoDatabaseURL() {
return mongoURL;
}
/**
* If true, we only allow the {@link VPNConfig#countryKickCommands()}. If false, we blacklist them.
* @return boolean
*/
public boolean getWhitelistCountries() {
return whitelistCountries;
}
/**
* Returns our configured commands to run on player country detection.
* @return List
*/
public List<String> countryKickCommands() {
return countryKickCommands;
}
/**
* Gets the port based on configuration. If {@link VPNConfig#port} is -1, will get default port
* based on {@link VPNConfig#getDatabaseType()} lowerCase().
* @return int
*/
public int getPort() {
if(port == -1) {
switch (getDatabaseType().toLowerCase()) {
case "mongodb":
case "mongo":
case "mongod":
return 27017;
case "sql":
case "mysql":
return 3306;
}
}
return port;
}
/**
* If true, <a href="https://bstats.org">...</a> metrics will be collected to improve KauriVPN.
* @return boolean
*/
public boolean metrics() {
return metrics;
}
/**
* Grabs all information from the config.yml
*/
public void update() {
license = licenseDefault.get();
kickMessage = kickStringDefault.get();
cacheResults = cacheResultsDefault.get();
prefixWhitelists = prefixWhitelistsDefault.get();
databaseEnabled = defaultDatabaseEnabled.get();
useCredentials = defaultUseCredentials.get();
databaseType = defaultDatabaseType.get();
databaseName = defaultDatabaseName.get();
mongoURL = defaultMongoURL.get();
username = defaultUsername.get();
password = defaultPassword.get();
ip = defaultIp.get();
port = defaultPort.get();
commandsEnabled = defaultCommandsEnable.get();
commands = defaultCommands.get();
kickPlayers = defaultKickPlayers.get();
alertToStaff = defaultAlertToStaff.get();
alertMsg = defaultAlertMsg.get();
metrics = defaultMetrics.get();
countryList = defCountrylist.get();
whitelistCountries = defaultWhitelistCountries.get();
countryKickCommands = defCountryKickCommands.get();
countryVanillaKickReason = defaultCountryKickReason.get();
}
/** Grabs all information from the config.yml */
public void update() {
license = licenseDefault.get();
kickMessage = kickStringDefault.get();
cacheResults = cacheResultsDefault.get();
prefixWhitelists = prefixWhitelistsDefault.get();
databaseEnabled = defaultDatabaseEnabled.get();
useCredentials = defaultUseCredentials.get();
databaseType = defaultDatabaseType.get();
databaseName = defaultDatabaseName.get();
mongoURL = defaultMongoURL.get();
username = defaultUsername.get();
password = defaultPassword.get();
ip = defaultIp.get();
port = defaultPort.get();
commandsEnabled = defaultCommandsEnable.get();
commands = defaultCommands.get();
kickPlayers = defaultKickPlayers.get();
alertToStaff = defaultAlertToStaff.get();
alertMsg = defaultAlertMsg.get();
metrics = defaultMetrics.get();
countryList = defCountrylist.get();
whitelistCountries = defaultWhitelistCountries.get();
countryKickCommands = defCountryKickCommands.get();
countryVanillaKickReason = defaultCountryKickReason.get();
}
} }
@@ -25,193 +25,166 @@ import dev.brighten.antivpn.utils.Tuple;
import dev.brighten.antivpn.utils.json.JSONException; import dev.brighten.antivpn.utils.json.JSONException;
import dev.brighten.antivpn.web.FunkemunkyAPI; import dev.brighten.antivpn.web.FunkemunkyAPI;
import dev.brighten.antivpn.web.objects.VPNResponse; import dev.brighten.antivpn.web.objects.VPNResponse;
import lombok.Getter;
import java.io.IOException; import java.io.IOException;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import java.util.*; import java.util.*;
import java.util.concurrent.*; import java.util.concurrent.*;
import java.util.logging.Level; import java.util.logging.Level;
import lombok.Getter;
@Getter @Getter
public abstract class VPNExecutor { public abstract class VPNExecutor {
private final ScheduledExecutorService threadExecutor = Executors.newScheduledThreadPool(2); private final ScheduledExecutorService threadExecutor = Executors.newScheduledThreadPool(2);
private final Set<UUID> whitelisted = Collections.synchronizedSet(new HashSet<>()); private final Set<UUID> whitelisted = Collections.synchronizedSet(new HashSet<>());
private final Set<CIDRUtils> whitelistedIps = Collections.synchronizedSet(new HashSet<>()); private final Set<CIDRUtils> whitelistedIps = Collections.synchronizedSet(new HashSet<>());
private final Queue<Tuple<CheckResult, UUID>> toKick = new LinkedBlockingQueue<>(); private final Queue<Tuple<CheckResult, UUID>> toKick = new LinkedBlockingQueue<>();
private final Queue<APIPlayer> playersToRecheck = new LinkedBlockingQueue<>(); private final Queue<APIPlayer> playersToRecheck = new LinkedBlockingQueue<>();
private ScheduledFuture<?> kickTask = null; private ScheduledFuture<?> kickTask = null;
public abstract void registerListeners();
public abstract void log(Level level, String log, Object... objects); public abstract void registerListeners();
public abstract void log(String log, Object... objects); public abstract void log(Level level, String log, Object... objects);
public abstract void logException(String message, Throwable ex); public abstract void log(String log, Object... objects);
public abstract void runCommand(String command); public abstract void logException(String message, Throwable ex);
public void logException(Throwable ex) { public abstract void runCommand(String command);
logException("An exception occurred: " + ex.getMessage(), ex);
}
public void startKickChecks() { public void logException(Throwable ex) {
kickTask = logException("An exception occurred: " + ex.getMessage(), ex);
threadExecutor.scheduleAtFixedRate( }
() -> {
synchronized (toKick) { public void startKickChecks() {
if (toKick.isEmpty()) return; kickTask = threadExecutor.scheduleAtFixedRate(() -> {
synchronized (toKick) {
if(toKick.isEmpty()) return;
Tuple<CheckResult, UUID> toCheck; Tuple<CheckResult, UUID> toCheck;
while ((toCheck = toKick.poll()) != null) { while((toCheck = toKick.poll()) != null) {
Optional<APIPlayer> player = Optional<APIPlayer> player = AntiVPN.getInstance().getPlayerExecutor().getPlayer(toCheck.second());
AntiVPN.getInstance().getPlayerExecutor().getPlayer(toCheck.second());
if (player.isEmpty()) { if(player.isEmpty()) {
continue; continue;
} }
handleKickingOfPlayer(toCheck.first(), player.get()); handleKickingOfPlayer(toCheck.first(), player.get());
} }
} }
}, }, 8, 2, TimeUnit.SECONDS);
8,
2,
TimeUnit.SECONDS);
}
public void handleKickingOfPlayer(CheckResult result, APIPlayer player) {
// Ensuring kick task is always running
if (kickTask == null || kickTask.isDone() || kickTask.isCancelled()) {
startKickChecks();
} }
if (AntiVPN.getInstance().getVpnConfig().isAlertToSTaff()) public void handleKickingOfPlayer(CheckResult result, APIPlayer player) {
AntiVPN.getInstance().getPlayerExecutor().getOnlinePlayers().stream()
.filter(APIPlayer::isAlertsEnabled)
.forEach(
pl ->
pl.sendMessage(
StringUtil.translateAlternateColorCodes(
'&',
StringUtil.varReplace(
dev.brighten.antivpn.AntiVPN.getInstance()
.getVpnConfig()
.getAlertMsg(),
player,
result.response()))));
if (AntiVPN.getInstance().getVpnConfig().isKickPlayers()) { //Ensuring kick task is always running
switch (result.resultType()) { if(kickTask == null || kickTask.isDone() || kickTask.isCancelled()) {
case DENIED_PROXY -> startKickChecks();
player.kickPlayer( }
StringUtil.varReplace(
AntiVPN.getInstance().getVpnConfig().getKickMessage(),
player,
result.response()));
case DENIED_COUNTRY ->
player.kickPlayer(
StringUtil.varReplace(
AntiVPN.getInstance().getVpnConfig().getCountryVanillaKickReason(),
player,
result.response()));
}
} else {
if (!AntiVPN.getInstance().getVpnConfig().isCommandsEnabled()) return;
}
Runnable runCommands = if (AntiVPN.getInstance().getVpnConfig().isAlertToSTaff()) AntiVPN.getInstance().getPlayerExecutor()
() -> { .getOnlinePlayers()
switch (result.resultType()) { .stream()
case DENIED_PROXY -> { .filter(APIPlayer::isAlertsEnabled)
for (String command : AntiVPN.getInstance().getVpnConfig().commands()) { .forEach(pl ->
runCommand(StringUtil.varReplace(command, player, result.response())); pl.sendMessage(StringUtil.translateAlternateColorCodes('&',
} StringUtil.varReplace(dev.brighten.antivpn.AntiVPN.getInstance().getVpnConfig()
.getAlertMsg(), player, result.response()))));
if(AntiVPN.getInstance().getVpnConfig().isKickPlayers()) {
switch (result.resultType()) {
case DENIED_PROXY -> player.kickPlayer(StringUtil.varReplace(AntiVPN.getInstance().getVpnConfig()
.getKickMessage(), player, result.response()));
case DENIED_COUNTRY -> player.kickPlayer(StringUtil.varReplace(AntiVPN.getInstance().getVpnConfig()
.getCountryVanillaKickReason(), player, result.response()));
} }
case DENIED_COUNTRY -> { } else {
for (String command : AntiVPN.getInstance().getVpnConfig().countryKickCommands()) { if(!AntiVPN.getInstance().getVpnConfig().isCommandsEnabled()) return;
runCommand(StringUtil.varReplace(command, player, result.response())); }
}
Runnable runCommands = () -> {
switch (result.resultType()) {
case DENIED_PROXY -> {
for (String command : AntiVPN.getInstance().getVpnConfig().commands()) {
runCommand(StringUtil.varReplace(command, player, result.response()));
}
}
case DENIED_COUNTRY -> {
for (String command : AntiVPN.getInstance().getVpnConfig().countryKickCommands()) {
runCommand(StringUtil.varReplace(command, player, result.response()));
}
}
} }
}
}; };
// Fixes the commands running too fast and causing messaging errors by any downstream plugins // Fixes the commands running too fast and causing messaging errors by any downstream plugins like LiteBans
// like LiteBans var scheduleResult = threadExecutor.schedule(runCommands, 1, TimeUnit.SECONDS);
var scheduleResult = threadExecutor.schedule(runCommands, 200, TimeUnit.MILLISECONDS);
if (scheduleResult.isCancelled()) { if(scheduleResult.isCancelled()) {
runCommands.run(); runCommands.run();
}
//Ensuring players are actually kicked as they are supposed to be.
toKick.add(new Tuple<>(result, player.getUuid()));
} }
var toAdd = new Tuple<>(result, player.getUuid()); public boolean isWhitelisted(UUID uuid) {
// Ensuring players are actually kicked as they are supposed to be. if(AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled()) {
threadExecutor.schedule( return AntiVPN.getInstance().getDatabase().isWhitelisted(uuid);
() -> { }
toKick.add(toAdd); return whitelisted.contains(uuid);
},
500,
TimeUnit.MILLISECONDS);
}
public boolean isWhitelisted(UUID uuid) {
if (AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled()) {
return AntiVPN.getInstance().getDatabase().isWhitelisted(uuid);
}
return whitelisted.contains(uuid);
}
public boolean isWhitelisted(String cidr) {
if (AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled()) {
return AntiVPN.getInstance().getDatabase().isWhitelisted(cidr);
}
try {
return whitelistedIps.contains(new CIDRUtils(cidr));
} catch (UnknownHostException e) {
throw new RuntimeException(e);
}
}
private final Cache<String, VPNResponse> cachedResponses =
Caffeine.newBuilder().expireAfterWrite(20, TimeUnit.MINUTES).maximumSize(4000).build();
public CompletableFuture<VPNResponse> checkIp(String ip) {
VPNResponse cached = cachedResponses.getIfPresent(ip);
if (cached != null) {
return CompletableFuture.completedFuture(cached);
} }
return CompletableFuture.supplyAsync( public boolean isWhitelisted(String cidr) {
() -> { if(AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled()) {
Optional<VPNResponse> cachedRes = return AntiVPN.getInstance().getDatabase().isWhitelisted(cidr);
AntiVPN.getInstance().getDatabase().getStoredResponse(ip); }
try {
return whitelistedIps.contains(new CIDRUtils(cidr));
} catch (UnknownHostException e) {
throw new RuntimeException(e);
}
}
if (cachedRes.isPresent()) { private final Cache<String, VPNResponse> cachedResponses = Caffeine.newBuilder()
return cachedRes.get(); .expireAfterWrite(20, TimeUnit.MINUTES)
} else { .maximumSize(4000)
try { .build();
VPNResponse response =
FunkemunkyAPI.getVPNResponse(
ip, AntiVPN.getInstance().getVpnConfig().getLicense(), true);
if (response.isSuccess()) { public CompletableFuture<VPNResponse> checkIp(String ip) {
AntiVPN.getInstance().getDatabase().cacheResponse(response); VPNResponse cached = cachedResponses.getIfPresent(ip);
} else {
log("Query to VPN API failed! Reason: " + response.getFailureReason());
}
return response; if(cached != null) {
} catch (JSONException | IOException e) { return CompletableFuture.completedFuture(cached);
log("Query to VPN API failed! Reason: " + e.getMessage()); }
return VPNResponse.FAILED_RESPONSE;
return CompletableFuture.supplyAsync(() -> {
Optional<VPNResponse> cachedRes = AntiVPN.getInstance().getDatabase().getStoredResponse(ip);
if(cachedRes.isPresent()) {
return cachedRes.get();
} }
} else {
}, try {
threadExecutor); VPNResponse response = FunkemunkyAPI
} .getVPNResponse(ip, AntiVPN.getInstance().getVpnConfig().getLicense(), true);
public abstract void disablePlugin(); if (response.isSuccess()) {
AntiVPN.getInstance().getDatabase().cacheResponse(response);
} else {
log("Query to VPN API failed! Reason: " + response.getFailureReason());
}
return response;
} catch (JSONException | IOException e) {
log("Query to VPN API failed! Reason: " + e.getMessage());
return VPNResponse.FAILED_RESPONSE;
}
}
}, threadExecutor);
}
public abstract void disablePlugin();
} }
@@ -20,21 +20,21 @@ import java.util.List;
public abstract class Command { public abstract class Command {
public abstract String permission(); public abstract String permission();
public abstract String name(); public abstract String name();
public abstract String[] aliases(); public abstract String[] aliases();
public abstract String description(); public abstract String description();
public abstract String usage(); public abstract String usage();
public abstract String parent(); public abstract String parent();
public abstract Command[] children(); public abstract Command[] children();
public abstract String execute(CommandExecutor executor, String[] args); public abstract String execute(CommandExecutor executor, String[] args);
public abstract List<String> tabComplete(CommandExecutor executor, String alias, String[] args); public abstract List<String> tabComplete(CommandExecutor executor, String alias, String[] args);
} }
@@ -17,15 +17,14 @@
package dev.brighten.antivpn.command; package dev.brighten.antivpn.command;
import dev.brighten.antivpn.api.APIPlayer; import dev.brighten.antivpn.api.APIPlayer;
import java.util.Optional; import java.util.Optional;
public interface CommandExecutor { public interface CommandExecutor {
void sendMessage(String message, Object... objects); void sendMessage(String message, Object... objects);
boolean hasPermission(String permission);
Optional<APIPlayer> getPlayer();
boolean isPlayer();
boolean hasPermission(String permission);
Optional<APIPlayer> getPlayer();
boolean isPlayer();
} }
@@ -21,68 +21,64 @@ import dev.brighten.antivpn.api.APIPlayer;
import dev.brighten.antivpn.command.Command; import dev.brighten.antivpn.command.Command;
import dev.brighten.antivpn.command.CommandExecutor; import dev.brighten.antivpn.command.CommandExecutor;
import dev.brighten.antivpn.message.VpnString; import dev.brighten.antivpn.message.VpnString;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
public class AlertsCommand extends Command { public class AlertsCommand extends Command {
@Override @Override
public String permission() { public String permission() {
return "antivpn.command.alerts"; return "antivpn.command.alerts";
} }
@Override @Override
public String name() { public String name() {
return "alerts"; return "alerts";
} }
@Override @Override
public String[] aliases() { public String[] aliases() {
return new String[] {"valerts", "vpnalerts"}; return new String[] {"valerts", "vpnalerts"};
} }
@Override @Override
public String description() { public String description() {
return "toggle VPN use alerts"; return "toggle VPN use alerts";
} }
@Override @Override
public String usage() { public String usage() {
return ""; return "";
} }
@Override @Override
public String parent() { public String parent() {
return "antivpn"; return "antivpn";
} }
@Override @Override
public Command[] children() { public Command[] children() {
return new Command[0]; return new Command[0];
} }
@Override @Override
public String execute(CommandExecutor executor, String[] args) { public String execute(CommandExecutor executor, String[] args) {
Optional<APIPlayer> pgetter = executor.getPlayer(); Optional<APIPlayer> pgetter = executor.getPlayer();
if (!pgetter.isPresent()) if(!pgetter.isPresent()) return AntiVPN.getInstance().getMessageHandler()
return AntiVPN.getInstance() .getString("command-misc-playerRequired").getMessage();
.getMessageHandler()
.getString("command-misc-playerRequired")
.getMessage();
APIPlayer player = pgetter.get(); APIPlayer player = pgetter.get();
player.setAlertsEnabled(!player.isAlertsEnabled()); player.setAlertsEnabled(!player.isAlertsEnabled());
player.updateAlertsState(); player.updateAlertsState();
return AntiVPN.getInstance() return AntiVPN.getInstance().getMessageHandler().getString("command-alerts-toggled")
.getMessageHandler() .getFormattedMessage(new VpnString.Var<>("state", player.isAlertsEnabled()));
.getString("command-alerts-toggled") }
.getFormattedMessage(new VpnString.Var<>("state", player.isAlertsEnabled()));
}
@Override @Override
public List<String> tabComplete(CommandExecutor executor, String alias, String[] args) { public List<String> tabComplete(CommandExecutor executor, String alias, String[] args) {
return Collections.emptyList(); return Collections.emptyList();
} }
} }
@@ -22,349 +22,315 @@ import dev.brighten.antivpn.command.Command;
import dev.brighten.antivpn.command.CommandExecutor; import dev.brighten.antivpn.command.CommandExecutor;
import dev.brighten.antivpn.utils.CIDRUtils; import dev.brighten.antivpn.utils.CIDRUtils;
import dev.brighten.antivpn.utils.MiscUtils; import dev.brighten.antivpn.utils.MiscUtils;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import java.util.*; import java.util.*;
import java.util.stream.Collectors; import java.util.stream.Collectors;
public class AllowlistCommand extends Command { public class AllowlistCommand extends Command {
private static final String[] secondArgs = new String[] {"add", "remove", "show", "search"}; private static final String[] secondArgs = new String[] {"add", "remove", "show", "search"};
@Override @Override
public String permission() { public String permission() {
return "antivpn.command.allowlist"; return "antivpn.command.allowlist";
}
@Override
public String name() {
return "allowlist";
}
@Override
public String[] aliases() {
return new String[] {"whitelist"};
}
@Override
public String description() {
return "Add/remove players to/from exemption list.";
}
@Override
public String usage() {
return "<add <player/uuid/ip> | remove <player/uuid/ip> | show [page] | search <query> [page]>";
}
@Override
public String parent() {
return "antivpn";
}
@Override
public Command[] children() {
return new Command[0];
}
@Override
public String execute(CommandExecutor executor, String[] args) {
if (args.length == 0
|| Arrays.stream(secondArgs).noneMatch(arg -> arg.equalsIgnoreCase(args[0]))) {
return "&cUsage: /antivpn allowlist " + usage();
} }
if (args[0].equalsIgnoreCase("show")) { @Override
// args[1] = optional page number (defaults to 1) public String name() {
int page = 1; return "allowlist";
if (args.length > 1) { }
@Override
public String[] aliases() {
return new String[] {"whitelist"};
}
@Override
public String description() {
return "Add/remove players to/from exemption list.";
}
@Override
public String usage() {
return "<add <player/uuid/ip> | remove <player/uuid/ip> | show [page] | search <query> [page]>";
}
@Override
public String parent() {
return "antivpn";
}
@Override
public Command[] children() {
return new Command[0];
}
@Override
public String execute(CommandExecutor executor, String[] args) {
if(args.length == 0 || Arrays.stream(secondArgs).noneMatch(arg -> arg.equalsIgnoreCase(args[0]))) {
return "&cUsage: /antivpn allowlist " + usage();
}
if(args[0].equalsIgnoreCase("show")) {
// args[1] = optional page number (defaults to 1)
int page = 1;
if (args.length > 1) {
try {
page = Integer.parseInt(args[1]);
if (page < 1) page = 1;
} catch (NumberFormatException e) {
return "&cUsage: /antivpn allowlist show [page]";
}
}
boolean databaseEnabled = AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled();
List<UUID> uuids = databaseEnabled
? AntiVPN.getInstance().getDatabase().getAllWhitelisted()
: new ArrayList<>(AntiVPN.getInstance().getExecutor().getWhitelisted());
List<CIDRUtils> ips = databaseEnabled
? AntiVPN.getInstance().getDatabase().getAllWhitelistedIps()
: new ArrayList<>(AntiVPN.getInstance().getExecutor().getWhitelistedIps());
List<String> entries = new ArrayList<>();
for (UUID uuid : uuids) {
entries.add("&7- &fUUID: &e" + uuid);
}
for (CIDRUtils cidr : ips) {
entries.add("&7- &fIP: &e" + cidr.getCidr());
}
return buildPage(entries, page, null, "show");
}
if(args[0].equalsIgnoreCase("search")) {
// args[1..n-1] = query terms; args[n] = optional page number if last arg is an integer
if (args.length < 2) {
return "&cUsage: /antivpn allowlist search <query> [page]";
}
// Detect optional trailing page number
int page = 1;
int queryEnd = args.length;
try {
int candidate = Integer.parseInt(args[args.length - 1]);
if (candidate >= 1 && args.length > 2) {
page = candidate;
queryEnd = args.length - 1;
}
} catch (NumberFormatException ignored) {}
String search = String.join(" ", Arrays.copyOfRange(args, 1, queryEnd)).toLowerCase();
// Strip color code characters to prevent formatting injection in output
String safeSearch = search.replace("&", "");
if (safeSearch.isEmpty()) {
return "&cUsage: /antivpn allowlist search <query> [page]";
}
boolean databaseEnabled = AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled();
List<UUID> uuids = databaseEnabled
? AntiVPN.getInstance().getDatabase().getAllWhitelisted()
: new ArrayList<>(AntiVPN.getInstance().getExecutor().getWhitelisted());
List<CIDRUtils> ips = databaseEnabled
? AntiVPN.getInstance().getDatabase().getAllWhitelistedIps()
: new ArrayList<>(AntiVPN.getInstance().getExecutor().getWhitelistedIps());
List<String> entries = new ArrayList<>();
for (UUID uuid : uuids) {
String entry = uuid.toString();
if (entry.toLowerCase().contains(search)) {
entries.add("&7- &fUUID: &e" + entry);
}
}
for (CIDRUtils cidr : ips) {
String entry = cidr.getCidr();
if (entry.toLowerCase().contains(search)) {
entries.add("&7- &fIP: &e" + entry);
}
}
return buildPage(entries, page, safeSearch, "search " + safeSearch);
}
if(args.length == 1)
return "&cYou have to provide a player to allow or deny exemption.";
boolean databaseEnabled = AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled();
if(!databaseEnabled) executor.sendMessage("&cThe database is currently not setup, " +
"so any changes here will disappear after a restart.");
CIDRUtils cidrUtils;
try { try {
page = Integer.parseInt(args[1]); cidrUtils = new CIDRUtils(args[1]);
if (page < 1) page = 1; } catch(IllegalArgumentException | UnknownHostException e) {
} catch (NumberFormatException e) { cidrUtils = null;
return "&cUsage: /antivpn allowlist show [page]";
} }
}
boolean databaseEnabled = AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled(); if(cidrUtils != null) {
if(!databaseEnabled) {
List<UUID> uuids = return switch (args[0].toLowerCase()) {
databaseEnabled case "add", "insert" -> {
? AntiVPN.getInstance().getDatabase().getAllWhitelisted() AntiVPN.getInstance().getExecutor().getWhitelistedIps().add(cidrUtils);
: new ArrayList<>(AntiVPN.getInstance().getExecutor().getWhitelisted()); yield String.format("&aAdded &6%s &ato exemption allowlist.", cidrUtils.getCidr());
List<CIDRUtils> ips = }
databaseEnabled case "remove", "delete" -> {
? AntiVPN.getInstance().getDatabase().getAllWhitelistedIps() AntiVPN.getInstance().getExecutor().getWhitelistedIps().remove(cidrUtils);
: new ArrayList<>(AntiVPN.getInstance().getExecutor().getWhitelistedIps()); yield String.format("&cRemoved &%s &cfrom the exemption allowlist.", cidrUtils.getCidr());
}
List<String> entries = new ArrayList<>(); default -> "&c\"" + args[0] + "\" is not a valid argument";
for (UUID uuid : uuids) { };
entries.add("&7- &fUUID: &e" + uuid); } else return switch (args[0].toLowerCase()) {
} case "add", "insert" -> {
for (CIDRUtils cidr : ips) { AntiVPN.getInstance().getExecutor().getWhitelistedIps().add(cidrUtils);
entries.add("&7- &fIP: &e" + cidr.getCidr()); AntiVPN.getInstance().getDatabase().addWhitelist(cidrUtils);
} yield String.format("&aAdded &6%s &ato exemption allowlist.", cidrUtils.getCidr());
}
return buildPage(entries, page, null, "show"); case "remove", "delete" -> {
} AntiVPN.getInstance().getExecutor().getWhitelistedIps().remove(cidrUtils);
AntiVPN.getInstance().getDatabase().removeWhitelist(cidrUtils);
if (args[0].equalsIgnoreCase("search")) { yield String.format("&cRemoved &6%s &cfrom the exemption allowlist.", cidrUtils.getCidr());
// args[1..n-1] = query terms; args[n] = optional page number if last arg is an integer }
if (args.length < 2) { default -> "&c\"" + args[0] + "\" is not a valid argument";
return "&cUsage: /antivpn allowlist search <query> [page]"; };
}
// Detect optional trailing page number
int page = 1;
int queryEnd = args.length;
try {
int candidate = Integer.parseInt(args[args.length - 1]);
if (candidate >= 1 && args.length > 2) {
page = candidate;
queryEnd = args.length - 1;
} }
} catch (NumberFormatException ignored) { if(MiscUtils.isIpv4(args[1])) {
} if(!databaseEnabled) {
try {
String search = String.join(" ", Arrays.copyOfRange(args, 1, queryEnd)).toLowerCase(); return switch(args[0].toLowerCase()) {
// Strip color code characters to prevent formatting injection in output case "add", "insert" -> {
String safeSearch = search.replace("&", ""); AntiVPN.getInstance().getExecutor().getWhitelistedIps().add(new CIDRUtils(args[1] + "/32"));
AntiVPN.getInstance().getDatabase().addWhitelist(new CIDRUtils(args[1] + "/32"));
if (safeSearch.isEmpty()) { yield String.format("&aAdded &6%s &ato the exemption allowlist.", args[1] + "/32");
return "&cUsage: /antivpn allowlist search <query> [page]"; }
} case "remove", "delete" -> {
AntiVPN.getInstance().getExecutor().getWhitelistedIps().remove(new CIDRUtils(args[1] + "/32"));
boolean databaseEnabled = AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled(); AntiVPN.getInstance().getDatabase().removeWhitelist(new CIDRUtils(args[1] + "/32"));
yield String.format("&cRemoved &6%s &cfrom the exemption allowlist.", args[1] + "/32");
List<UUID> uuids = }
databaseEnabled default -> "&c\"" + args[0] + "\" is not a valid argument";
? AntiVPN.getInstance().getDatabase().getAllWhitelisted() };
: new ArrayList<>(AntiVPN.getInstance().getExecutor().getWhitelisted()); } catch (UnknownHostException e) {
List<CIDRUtils> ips = AntiVPN.getInstance().getExecutor().logException("Invalid IP format for allowlist command", e);
databaseEnabled return "&cInvalid IP format for allowlist command";
? AntiVPN.getInstance().getDatabase().getAllWhitelistedIps() }
: new ArrayList<>(AntiVPN.getInstance().getExecutor().getWhitelistedIps()); } else {
try {
List<String> entries = new ArrayList<>(); return switch (args[0].toLowerCase()) {
for (UUID uuid : uuids) { case "add", "insert" -> {
String entry = uuid.toString(); AntiVPN.getInstance().getDatabase().addWhitelist(new CIDRUtils(args[1] + "/32"));
if (entry.toLowerCase().contains(search)) { yield String.format("&aAdded &6%s &a to the exemption allowlist.", args[1] + "/32");
entries.add("&7- &fUUID: &e" + entry); }
} case "remove", "delete" -> {
} AntiVPN.getInstance().getDatabase().removeWhitelist(new CIDRUtils(args[1] + "/32"));
for (CIDRUtils cidr : ips) { yield String.format("&cRemoved &6%s &c from the exemption allowlist.", args[1] + "/32");
String entry = cidr.getCidr(); }
if (entry.toLowerCase().contains(search)) { default -> "&c\"" + args[0] + "\" is not a valid argument";
entries.add("&7- &fIP: &e" + entry); };
} } catch (UnknownHostException e) {
} AntiVPN.getInstance().getExecutor().logException("Invalid IP format for allowlist command", e);
return "&cInvalid IP format for allowlist command";
return buildPage(entries, page, safeSearch, "search " + safeSearch); }
}
if (args.length == 1) return "&cYou have to provide a player to allow or deny exemption.";
boolean databaseEnabled = AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled();
if (!databaseEnabled)
executor.sendMessage(
"&cThe database is currently not setup, "
+ "so any changes here will disappear after a restart.");
CIDRUtils cidrUtils;
try {
cidrUtils = new CIDRUtils(args[1]);
} catch (IllegalArgumentException | UnknownHostException e) {
cidrUtils = null;
}
if (cidrUtils != null) {
if (!databaseEnabled) {
return switch (args[0].toLowerCase()) {
case "add", "insert" -> {
AntiVPN.getInstance().getExecutor().getWhitelistedIps().add(cidrUtils);
yield String.format("&aAdded &6%s &ato exemption allowlist.", cidrUtils.getCidr());
}
case "remove", "delete" -> {
AntiVPN.getInstance().getExecutor().getWhitelistedIps().remove(cidrUtils);
yield String.format(
"&cRemoved &%s &cfrom the exemption allowlist.", cidrUtils.getCidr());
}
default -> "&c\"" + args[0] + "\" is not a valid argument";
};
} else
return switch (args[0].toLowerCase()) {
case "add", "insert" -> {
AntiVPN.getInstance().getExecutor().getWhitelistedIps().add(cidrUtils);
AntiVPN.getInstance().getDatabase().addWhitelist(cidrUtils);
yield String.format("&aAdded &6%s &ato exemption allowlist.", cidrUtils.getCidr());
}
case "remove", "delete" -> {
AntiVPN.getInstance().getExecutor().getWhitelistedIps().remove(cidrUtils);
AntiVPN.getInstance().getDatabase().removeWhitelist(cidrUtils);
yield String.format(
"&cRemoved &6%s &cfrom the exemption allowlist.", cidrUtils.getCidr());
}
default -> "&c\"" + args[0] + "\" is not a valid argument";
};
}
if (MiscUtils.isIpv4(args[1])) {
if (!databaseEnabled) {
try {
return switch (args[0].toLowerCase()) {
case "add", "insert" -> {
AntiVPN.getInstance()
.getExecutor()
.getWhitelistedIps()
.add(new CIDRUtils(args[1] + "/32"));
AntiVPN.getInstance().getDatabase().addWhitelist(new CIDRUtils(args[1] + "/32"));
yield String.format("&aAdded &6%s &ato the exemption allowlist.", args[1] + "/32");
} }
case "remove", "delete" -> {
AntiVPN.getInstance()
.getExecutor()
.getWhitelistedIps()
.remove(new CIDRUtils(args[1] + "/32"));
AntiVPN.getInstance().getDatabase().removeWhitelist(new CIDRUtils(args[1] + "/32"));
yield String.format(
"&cRemoved &6%s &cfrom the exemption allowlist.", args[1] + "/32");
}
default -> "&c\"" + args[0] + "\" is not a valid argument";
};
} catch (UnknownHostException e) {
AntiVPN.getInstance()
.getExecutor()
.logException("Invalid IP format for allowlist command", e);
return "&cInvalid IP format for allowlist command";
}
} else {
try {
return switch (args[0].toLowerCase()) {
case "add", "insert" -> {
AntiVPN.getInstance().getDatabase().addWhitelist(new CIDRUtils(args[1] + "/32"));
yield String.format("&aAdded &6%s &a to the exemption allowlist.", args[1] + "/32");
}
case "remove", "delete" -> {
AntiVPN.getInstance().getDatabase().removeWhitelist(new CIDRUtils(args[1] + "/32"));
yield String.format(
"&cRemoved &6%s &c from the exemption allowlist.", args[1] + "/32");
}
default -> "&c\"" + args[0] + "\" is not a valid argument";
};
} catch (UnknownHostException e) {
AntiVPN.getInstance()
.getExecutor()
.logException("Invalid IP format for allowlist command", e);
return "&cInvalid IP format for allowlist command";
}
}
} else {
UUID uuid;
try {
uuid = UUID.fromString(args[1]);
} catch (IllegalArgumentException e) {
Optional<APIPlayer> player = AntiVPN.getInstance().getPlayerExecutor().getPlayer(args[1]);
if (player.isPresent()) {
uuid = player.get().getUuid();
} else { } else {
uuid = MiscUtils.lookupUUID(args[1]); UUID uuid;
if (uuid == null) { try {
return "&cCould not find a UUID for \"" uuid = UUID.fromString(args[1]);
+ args[1] } catch(IllegalArgumentException e) {
+ "\". They might not have provided a valid username."; Optional<APIPlayer> player = AntiVPN.getInstance().getPlayerExecutor().getPlayer(args[1]);
} if (player.isPresent()) {
} uuid = player.get().getUuid();
} } else {
uuid = MiscUtils.lookupUUID(args[1]);
if (uuid == null) {
return "&cCould not find a UUID for \"" + args[1] + "\". They might not have provided a valid username.";
}
}
}
if (!databaseEnabled) { if(!databaseEnabled) {
return switch (args[0].toLowerCase()) { return switch (args[0].toLowerCase()) {
case "add" -> { case "add" -> {
AntiVPN.getInstance().getExecutor().getWhitelisted().add(uuid); AntiVPN.getInstance().getExecutor().getWhitelisted().add(uuid);
yield String.format("&aAdded &6%s &auuid to the exemption allowlist.", uuid.toString()); yield String.format("&aAdded &6%s &auuid to the exemption allowlist.", uuid.toString());
} }
case "remove", "delete" -> { case "remove", "delete" -> {
AntiVPN.getInstance().getExecutor().getWhitelisted().remove(uuid); AntiVPN.getInstance().getExecutor().getWhitelisted().remove(uuid);
yield String.format( yield String.format("&cRemoved &6%s &cuuid from the exemption allowlist.", uuid.toString());
"&cRemoved &6%s &cuuid from the exemption allowlist.", uuid.toString()); }
} default -> "&c\"" + args[0] + "\" is not a valid argument";
default -> "&c\"" + args[0] + "\" is not a valid argument"; };
}; } else {
} else { return switch (args[0].toLowerCase()) {
return switch (args[0].toLowerCase()) { case "add" -> {
case "add" -> { AntiVPN.getInstance().getDatabase().addWhitelist(uuid);
AntiVPN.getInstance().getDatabase().addWhitelist(uuid); yield String.format("&aAdded &6%s &auuid to the exemption allowlist.", uuid.toString());
yield String.format("&aAdded &6%s &auuid to the exemption allowlist.", uuid.toString()); }
} case "remove", "delete" -> {
case "remove", "delete" -> { AntiVPN.getInstance().getDatabase().removeWhitelist(uuid);
AntiVPN.getInstance().getDatabase().removeWhitelist(uuid); yield String.format("&cRemoved &6%s &cuuid from the exemption allowlist.", uuid.toString());
yield String.format( }
"&cRemoved &6%s &cuuid from the exemption allowlist.", uuid.toString()); default -> "&c\"" + args[0] + "\" is not a valid argument";
} };
default -> "&c\"" + args[0] + "\" is not a valid argument"; }
}; }
}
} }
}
@Override @Override
public List<String> tabComplete(CommandExecutor executor, String alias, String[] args) { public List<String> tabComplete(CommandExecutor executor, String alias, String[] args) {
return switch (args.length) { return switch (args.length) {
case 1 -> case 1 -> Arrays.stream(secondArgs)
Arrays.stream(secondArgs) .filter(narg -> narg.toLowerCase().startsWith(args[0].toLowerCase()))
.filter(narg -> narg.toLowerCase().startsWith(args[0].toLowerCase())) .collect(Collectors.toList());
.collect(Collectors.toList()); case 2 -> {
case 2 -> { if (args[0].equalsIgnoreCase("show") || args[0].equalsIgnoreCase("search")) {
if (args[0].equalsIgnoreCase("show") || args[0].equalsIgnoreCase("search")) { yield Collections.emptyList();
yield Collections.emptyList(); }
} yield AntiVPN.getInstance().getPlayerExecutor().getOnlinePlayers().stream()
yield AntiVPN.getInstance().getPlayerExecutor().getOnlinePlayers().stream() .map(APIPlayer::getName)
.map(APIPlayer::getName) .filter(name -> name.toLowerCase().startsWith(args[1].toLowerCase()))
.filter(name -> name.toLowerCase().startsWith(args[1].toLowerCase())) .collect(Collectors.toList());
.collect(Collectors.toList()); }
} default -> Collections.emptyList();
default -> Collections.emptyList(); };
}; }
}
private String buildPage( private String buildPage(List<String> entries, int page, String safeSearch, String subcommandPrefix) {
List<String> entries, int page, String safeSearch, String subcommandPrefix) { int pageSize = 10;
int pageSize = 10; int totalPages = Math.max(1, (entries.size() + pageSize - 1) / pageSize);
int totalPages = Math.max(1, (entries.size() + pageSize - 1) / pageSize); if (page > totalPages) page = totalPages;
if (page > totalPages) page = totalPages;
List<String> messages = new ArrayList<>(); List<String> messages = new ArrayList<>();
messages.add("&8&m-----------------------------------------------------"); messages.add("&8&m-----------------------------------------------------");
messages.add( messages.add("&6&lAllowlist Entries &8(&7Page &f" + page + "&7/&f" + totalPages + "&8)"
"&6&lAllowlist Entries &8(&7Page &f" + (safeSearch != null ? " &7(search: &f" + safeSearch + "&7)" : ""));
+ page
+ "&7/&f"
+ totalPages
+ "&8)"
+ (safeSearch != null ? " &7(search: &f" + safeSearch + "&7)" : ""));
messages.add("");
if (entries.isEmpty()) {
messages.add(
safeSearch != null
? "&cNo allowlist entries matching &f\"" + safeSearch + "&c\" were found."
: "&cThe allowlist is empty.");
} else {
int start = (page - 1) * pageSize;
int end = Math.min(start + pageSize, entries.size());
for (int i = start; i < end; i++) {
messages.add(entries.get(i));
}
if (totalPages > 1) {
messages.add(""); messages.add("");
if (page > 1) {
messages.add( if (entries.isEmpty()) {
"&7Previous page: &f/antivpn allowlist " + subcommandPrefix + " " + (page - 1)); messages.add(safeSearch != null
? "&cNo allowlist entries matching &f\"" + safeSearch + "&c\" were found."
: "&cThe allowlist is empty.");
} else {
int start = (page - 1) * pageSize;
int end = Math.min(start + pageSize, entries.size());
for (int i = start; i < end; i++) {
messages.add(entries.get(i));
}
if (totalPages > 1) {
messages.add("");
if (page > 1) {
messages.add("&7Previous page: &f/antivpn allowlist " + subcommandPrefix + " " + (page - 1));
}
if (page < totalPages) {
messages.add("&7Next page: &f/antivpn allowlist " + subcommandPrefix + " " + (page + 1));
}
}
} }
if (page < totalPages) { messages.add("&8&m-----------------------------------------------------");
messages.add("&7Next page: &f/antivpn allowlist " + subcommandPrefix + " " + (page + 1)); return String.join("\n", messages);
}
}
} }
messages.add("&8&m-----------------------------------------------------");
return String.join("\n", messages);
}
} }
@@ -20,6 +20,7 @@ import dev.brighten.antivpn.AntiVPN;
import dev.brighten.antivpn.command.Command; import dev.brighten.antivpn.command.Command;
import dev.brighten.antivpn.command.CommandExecutor; import dev.brighten.antivpn.command.CommandExecutor;
import dev.brighten.antivpn.utils.StringUtil; import dev.brighten.antivpn.utils.StringUtil;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
@@ -27,95 +28,73 @@ import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
public class AntiVPNCommand extends Command { public class AntiVPNCommand extends Command {
@Override @Override
public String permission() { public String permission() {
return "antivpn.command"; return "antivpn.command";
}
@Override
public String name() {
return "antivpn";
}
@Override
public String[] aliases() {
return new String[] {"kaurivpn", "kvpn", "vpn", "avpn"};
}
@Override
public String description() {
return "The main help command";
}
@Override
public String usage() {
return "";
}
@Override
public String parent() {
return "";
}
@Override
public Command[] children() {
return new Command[] {
new LookupCommand(),
new AllowlistCommand(),
new AlertsCommand(),
new ClearCacheCommand(),
new PlanCommand(),
new ReloadCommand()
};
}
@Override
public String execute(CommandExecutor uuid, String[] args) {
List<String> messages = new ArrayList<>();
messages.add(StringUtil.line("&8"));
messages.add("&6&lAntiVPN Help Page");
messages.add("");
for (Command cmd : AntiVPN.getInstance().getCommands()) {
messages.add(
String.format(
"&8/&f%s &8- &7&o%s",
"&7"
+ cmd.parent()
+ (cmd.parent().length() > 0 ? " " : "")
+ "&f"
+ cmd.name()
+ " &7"
+ cmd.usage(),
cmd.description()));
}
for (Command child : children()) {
messages.add(
String.format(
"&8/&f%s &8- &7&o%s",
"&7"
+ child.parent()
+ (child.parent().length() > 0 ? " " : "")
+ "&f"
+ child.name()
+ " &7"
+ child.usage(),
child.description()));
} }
messages.add(StringUtil.line("&8")); @Override
public String name() {
return "antivpn";
}
return String.join("\n", messages); @Override
} public String[] aliases() {
return new String[] {"kaurivpn", "kvpn", "vpn", "avpn"};
}
@Override @Override
public List<String> tabComplete(CommandExecutor executor, String alias, String[] args) { public String description() {
if (args.length == 1) return "The main help command";
return Arrays.stream(children()) }
.map(Command::name)
.filter(name -> name.toLowerCase().startsWith(args[0].toLowerCase()))
.collect(Collectors.toList());
return Collections.emptyList(); @Override
} public String usage() {
return "";
}
@Override
public String parent() {
return "";
}
@Override
public Command[] children() {
return new Command[] {new LookupCommand(), new AllowlistCommand(), new AlertsCommand(),
new ClearCacheCommand(), new PlanCommand(), new ReloadCommand()};
}
@Override
public String execute(CommandExecutor uuid, String[] args) {
List<String> messages = new ArrayList<>();
messages.add(StringUtil.line("&8"));
messages.add("&6&lAntiVPN Help Page");
messages.add("");
for (Command cmd : AntiVPN.getInstance().getCommands()) {
messages.add(String.format("&8/&f%s &8- &7&o%s", "&7" + cmd.parent()
+ (cmd.parent().length() > 0 ? " " : "") + "&f" + cmd.name() + " &7"
+ cmd.usage(), cmd.description()));
}
for (Command child : children()) {
messages.add(String.format("&8/&f%s &8- &7&o%s", "&7" + child.parent()
+ (child.parent().length() > 0 ? " " : "") + "&f" + child.name() + " &7"
+ child.usage(), child.description()));
}
messages.add(StringUtil.line("&8"));
return String.join("\n", messages);
}
@Override
public List<String> tabComplete(CommandExecutor executor, String alias, String[] args) {
if(args.length == 1)
return Arrays.stream(children())
.map(Command::name)
.filter(name -> name.toLowerCase().startsWith(args[0].toLowerCase()))
.collect(Collectors.toList());
return Collections.emptyList();
}
} }
@@ -17,58 +17,57 @@
package dev.brighten.antivpn.command.impl; package dev.brighten.antivpn.command.impl;
import dev.brighten.antivpn.AntiVPN; import dev.brighten.antivpn.AntiVPN;
import dev.brighten.antivpn.api.VPNExecutor;
import dev.brighten.antivpn.command.Command; import dev.brighten.antivpn.command.Command;
import dev.brighten.antivpn.command.CommandExecutor; import dev.brighten.antivpn.command.CommandExecutor;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
public class ClearCacheCommand extends Command { public class ClearCacheCommand extends Command {
@Override @Override
public String permission() { public String permission() {
return "antivpn.command.clearcache"; return "antivpn.command.clearcache";
} }
@Override @Override
public String name() { public String name() {
return "clearcache"; return "clearcache";
} }
@Override @Override
public String[] aliases() { public String[] aliases() {
return new String[] {"clear", "cc"}; return new String[] {"clear", "cc"};
} }
@Override @Override
public String description() { public String description() {
return "Clear the API response cache if you're having problems."; return "Clear the API response cache if you're having problems.";
} }
@Override @Override
public String usage() { public String usage() {
return ""; return "";
} }
@Override @Override
public String parent() { public String parent() {
return "antivpn"; return "antivpn";
} }
@Override @Override
public Command[] children() { public Command[] children() {
return new Command[0]; return new Command[0];
} }
@Override @Override
public String execute(CommandExecutor executor, String[] args) { public String execute(CommandExecutor executor, String[] args) {
AntiVPN.getInstance() AntiVPN.getInstance().getExecutor().getThreadExecutor().execute(() -> AntiVPN.getInstance().getDatabase().clearResponses());
.getExecutor() return "&aCleared all cached API response information!";
.getThreadExecutor() }
.execute(() -> AntiVPN.getInstance().getDatabase().clearResponses());
return "&aCleared all cached API response information!";
}
@Override @Override
public List<String> tabComplete(CommandExecutor executor, String alias, String[] args) { public List<String> tabComplete(CommandExecutor executor, String alias, String[] args) {
return Collections.emptyList(); return Collections.emptyList();
} }
} }
@@ -21,97 +21,94 @@ import dev.brighten.antivpn.api.APIPlayer;
import dev.brighten.antivpn.command.Command; import dev.brighten.antivpn.command.Command;
import dev.brighten.antivpn.command.CommandExecutor; import dev.brighten.antivpn.command.CommandExecutor;
import dev.brighten.antivpn.utils.StringUtil; import dev.brighten.antivpn.utils.StringUtil;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.stream.Collectors; import java.util.stream.Collectors;
public class LookupCommand extends Command { public class LookupCommand extends Command {
@Override @Override
public String permission() { public String permission() {
return "antivpn.command.lookup"; return "antivpn.command.lookup";
}
@Override
public String name() {
return "lookup";
}
@Override
public String[] aliases() {
return new String[] {"check"};
}
@Override
public String description() {
return "Lookup a player's ip info";
}
@Override
public String usage() {
return "<player>";
}
@Override
public String parent() {
return "antivpn";
}
@Override
public Command[] children() {
return new Command[0];
}
@Override
public String execute(CommandExecutor executor, String[] args) {
if (args.length == 0) {
return "&cPlease supply a player to check.";
} }
Optional<APIPlayer> player = AntiVPN.getInstance().getPlayerExecutor().getPlayer(args[0]); @Override
public String name() {
if (player.isEmpty()) { return "lookup";
return String.format("&cNo player found with the name \"%s\"", args[0]);
} }
AntiVPN.getInstance() @Override
.getExecutor() public String[] aliases() {
.checkIp(player.get().getIp().getHostAddress()) return new String[] {"check"};
.thenAccept( }
result -> {
if (!result.isSuccess()) {
executor.sendMessage(
"&cThere was an error trying to find the " + "information of this player.");
return;
}
executor.sendMessage(StringUtil.line("&8")); @Override
executor.sendMessage( public String description() {
"&6&l" + player.get().getName() + "&7&l's Connection Information"); return "Lookup a player's ip info";
executor.sendMessage(""); }
executor.sendMessage(
"&e%s&8: &f%s", "Proxy", result.isProxy() ? "&a" + result.getMethod() : "&cNo");
executor.sendMessage("&e%s&8: &f%s", "ISP", result.getIsp());
executor.sendMessage("&e%s&8: &f%s", "Country", result.getCountryName());
executor.sendMessage("&e%s&8: &f%s", "City", result.getCity());
executor.sendMessage(
"&e%s&8: &f%s",
"Coordinates", result.getLatitude() + "&7/&f" + result.getLongitude());
executor.sendMessage(StringUtil.line("&8"));
});
return "&7Looking up the IP information for player " + player.get().getName() + "..."; @Override
} public String usage() {
return "<player>";
}
@Override @Override
public List<String> tabComplete(CommandExecutor executor, String alias, String[] args) { public String parent() {
return "antivpn";
}
if (args.length == 1) @Override
return AntiVPN.getInstance().getPlayerExecutor().getOnlinePlayers().stream() public Command[] children() {
.map(APIPlayer::getName) return new Command[0];
.filter(name -> name.toLowerCase().startsWith(args[0].toLowerCase())) }
.collect(Collectors.toList());
return Collections.emptyList(); @Override
} public String execute(CommandExecutor executor, String[] args) {
if(args.length == 0) {
return "&cPlease supply a player to check.";
}
Optional<APIPlayer> player = AntiVPN.getInstance().getPlayerExecutor().getPlayer(args[0]);
if(player.isEmpty()) {
return String.format("&cNo player found with the name \"%s\"", args[0]);
}
AntiVPN.getInstance().getExecutor()
.checkIp(player.get().getIp().getHostAddress())
.thenAccept(result -> {
if(!result.isSuccess()) {
executor.sendMessage("&cThere was an error trying to find the " +
"information of this player.");
return;
}
executor.sendMessage(StringUtil.line("&8"));
executor.sendMessage("&6&l" + player.get().getName() + "&7&l's Connection Information");
executor.sendMessage("");
executor.sendMessage("&e%s&8: &f%s", "Proxy", result.isProxy()
? "&a" + result.getMethod() : "&cNo");
executor.sendMessage("&e%s&8: &f%s", "ISP", result.getIsp());
executor.sendMessage("&e%s&8: &f%s", "Country", result.getCountryName());
executor.sendMessage("&e%s&8: &f%s", "City", result.getCity());
executor.sendMessage("&e%s&8: &f%s", "Coordinates", result.getLatitude()
+ "&7/&f" + result.getLongitude());
executor.sendMessage(StringUtil.line("&8"));
});
return "&7Looking up the IP information for player " + player.get().getName() + "...";
}
@Override
public List<String> tabComplete(CommandExecutor executor, String alias, String[] args) {
if(args.length == 1) return AntiVPN.getInstance().getPlayerExecutor().getOnlinePlayers().stream()
.map(APIPlayer::getName)
.filter(name -> name.toLowerCase().startsWith(args[0].toLowerCase()))
.collect(Collectors.toList());
return Collections.emptyList();
}
} }
@@ -17,110 +17,101 @@
package dev.brighten.antivpn.command.impl; package dev.brighten.antivpn.command.impl;
import dev.brighten.antivpn.AntiVPN; import dev.brighten.antivpn.AntiVPN;
import dev.brighten.antivpn.api.VPNExecutor;
import dev.brighten.antivpn.command.Command; import dev.brighten.antivpn.command.Command;
import dev.brighten.antivpn.command.CommandExecutor; import dev.brighten.antivpn.command.CommandExecutor;
import dev.brighten.antivpn.utils.StringUtil; import dev.brighten.antivpn.utils.StringUtil;
import dev.brighten.antivpn.utils.json.JSONException; import dev.brighten.antivpn.utils.json.JSONException;
import dev.brighten.antivpn.web.FunkemunkyAPI; import dev.brighten.antivpn.web.FunkemunkyAPI;
import dev.brighten.antivpn.web.objects.QueryResponse; import dev.brighten.antivpn.web.objects.QueryResponse;
import java.io.IOException; import java.io.IOException;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
public class PlanCommand extends Command { public class PlanCommand extends Command {
@Override @Override
public String permission() { public String permission() {
return "antivpn.command.plan"; return "antivpn.command.plan";
} }
@Override @Override
public String name() { public String name() {
return "plan"; return "plan";
} }
@Override @Override
public String[] aliases() { public String[] aliases() {
return new String[] {"queries", "query"}; return new String[] {"queries", "query"};
} }
@Override @Override
public String description() { public String description() {
return "Info related to KauriVPN Plan"; return "Info related to KauriVPN Plan";
} }
@Override @Override
public String usage() { public String usage() {
return ""; return "";
} }
@Override @Override
public String parent() { public String parent() {
return "antivpn"; return "antivpn";
} }
@Override @Override
public Command[] children() { public Command[] children() {
return new Command[0]; return new Command[0];
} }
@Override
public String execute(CommandExecutor executor, String[] args) {
AntiVPN.getInstance()
.getExecutor()
.getThreadExecutor()
.execute(
() -> {
QueryResponse result;
try {
if (AntiVPN.getInstance().getVpnConfig().getLicense().isEmpty()) {
result = FunkemunkyAPI.getQueryResponse();
} else {
result =
FunkemunkyAPI.getQueryResponse(
AntiVPN.getInstance().getVpnConfig().getLicense());
if (!result.isValidPlan()) {
executor.sendMessage(
"&cThe license &f%s &cis not a valid license, "
+ "checking your Free plan information...",
AntiVPN.getInstance().getVpnConfig().getLicense());
@Override
public String execute(CommandExecutor executor, String[] args) {
AntiVPN.getInstance().getExecutor().getThreadExecutor().execute(() -> {
QueryResponse result;
try {
if(AntiVPN.getInstance().getVpnConfig().getLicense().isEmpty()) {
result = FunkemunkyAPI.getQueryResponse(); result = FunkemunkyAPI.getQueryResponse();
} } else {
result = FunkemunkyAPI.getQueryResponse(AntiVPN.getInstance().getVpnConfig().getLicense());
if(!result.isValidPlan()) {
executor.sendMessage("&cThe license &f%s &cis not a valid license, " +
"checking your Free plan information...",
AntiVPN.getInstance().getVpnConfig().getLicense());
result = FunkemunkyAPI.getQueryResponse();
}
} }
String plan = result.getPlanType(); String plan = result.getPlanType();
if (plan.equals("IP")) plan += " (Free)"; if(plan.equals("IP")) plan+= " (Free)";
String queryMax = String queryMax = result.getQueriesMax() == Long.MAX_VALUE
result.getQueriesMax() == Long.MAX_VALUE ? "Unlimited" : String.valueOf(result.getQueriesMax());
? "Unlimited"
: String.valueOf(result.getQueriesMax());
executor.sendMessage(StringUtil.line("&8")); executor.sendMessage(StringUtil.line("&8"));
executor.sendMessage("&6&lKauriVPN Plan Information"); executor.sendMessage("&6&lKauriVPN Plan Information");
executor.sendMessage(""); executor.sendMessage("");
executor.sendMessage("&e%s&8: &f%s", "Plan", plan); executor.sendMessage("&e%s&8: &f%s", "Plan", plan);
executor.sendMessage( executor.sendMessage("&e%s&8: &f%s&7/&f%s", "Queries Used",
"&e%s&8: &f%s&7/&f%s", "Queries Used", result.getQueries(), queryMax); result.getQueries(), queryMax);
executor.sendMessage(StringUtil.line("&8")); executor.sendMessage(StringUtil.line("&8"));
} catch (JSONException e) { } catch(JSONException e) {
AntiVPN.getInstance().getExecutor().logException(e); AntiVPN.getInstance().getExecutor().logException(e);
executor.sendMessage( executor.sendMessage("&cThere was a JSONException thrown while looking up your query " +
"&cThere was a JSONException thrown while looking up your query " "information. Check console for more details.");
+ "information. Check console for more details."); } catch (IOException e) {
} catch (IOException e) {
AntiVPN.getInstance().getExecutor().logException(e); AntiVPN.getInstance().getExecutor().logException(e);
executor.sendMessage( executor.sendMessage("&cThere was a IOException thrown while looking up your query " +
"&cThere was a IOException thrown while looking up your query " "information. Check console for more details.");
+ "information. Check console for more details."); }
} });
}); return "&7Looking up your query information...";
return "&7Looking up your query information..."; }
}
@Override @Override
public List<String> tabComplete(CommandExecutor executor, String alias, String[] args) { public List<String> tabComplete(CommandExecutor executor, String alias, String[] args) {
return Collections.emptyList(); return Collections.emptyList();
} }
} }
@@ -19,65 +19,63 @@ package dev.brighten.antivpn.command.impl;
import dev.brighten.antivpn.AntiVPN; import dev.brighten.antivpn.AntiVPN;
import dev.brighten.antivpn.command.Command; import dev.brighten.antivpn.command.Command;
import dev.brighten.antivpn.command.CommandExecutor; import dev.brighten.antivpn.command.CommandExecutor;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
public class ReloadCommand extends Command { public class ReloadCommand extends Command {
@Override @Override
public String permission() { public String permission() {
return "antivpn.command.reload"; return "antivpn.command.reload";
} }
@Override @Override
public String name() { public String name() {
return "reload"; return "reload";
} }
@Override @Override
public String[] aliases() { public String[] aliases() {
return new String[0]; return new String[0];
} }
@Override @Override
public String description() { public String description() {
return "Reload the plugin"; return "Reload the plugin";
} }
@Override @Override
public String usage() { public String usage() {
return ""; return "";
} }
@Override @Override
public String parent() { public String parent() {
return "antivpn"; return "antivpn";
} }
@Override @Override
public Command[] children() { public Command[] children() {
return new Command[0]; return new Command[0];
} }
@Override @Override
public String execute(CommandExecutor executor, String[] args) { public String execute(CommandExecutor executor, String[] args) {
// Loading changes from the config.yml // Loading changes from the config.yml
AntiVPN.getInstance().reloadConfig(); AntiVPN.getInstance().reloadConfig();
// Updating the cache of these values in VPNConfig // Updating the cache of these values in VPNConfig
AntiVPN.getInstance().getVpnConfig().update(); AntiVPN.getInstance().getVpnConfig().update();
AntiVPN.getInstance().getMessageHandler().reloadStrings(); AntiVPN.getInstance().getMessageHandler().reloadStrings();
AntiVPN.getInstance().reloadDatabase(); AntiVPN.getInstance().reloadDatabase();
return AntiVPN.getInstance() return AntiVPN.getInstance().getMessageHandler().getString("command-reload-complete").getMessage();
.getMessageHandler() }
.getString("command-reload-complete")
.getMessage();
}
@Override @Override
public List<String> tabComplete(CommandExecutor executor, String alias, String[] args) { public List<String> tabComplete(CommandExecutor executor, String alias, String[] args) {
return Collections.emptyList(); return Collections.emptyList();
} }
} }
@@ -17,7 +17,7 @@
package dev.brighten.antivpn.database; package dev.brighten.antivpn.database;
public class DatabaseException extends RuntimeException { public class DatabaseException extends RuntimeException {
public DatabaseException(String message, Throwable e) { public DatabaseException(String message, Throwable e) {
super(message, e); super(message, e);
} }
} }
@@ -18,43 +18,44 @@ package dev.brighten.antivpn.database;
import dev.brighten.antivpn.utils.CIDRUtils; import dev.brighten.antivpn.utils.CIDRUtils;
import dev.brighten.antivpn.web.objects.VPNResponse; import dev.brighten.antivpn.web.objects.VPNResponse;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.UUID; import java.util.UUID;
import java.util.function.Consumer; import java.util.function.Consumer;
public interface VPNDatabase { public interface VPNDatabase {
Optional<VPNResponse> getStoredResponse(String ip); Optional<VPNResponse> getStoredResponse(String ip);
void cacheResponse(VPNResponse toCache); void cacheResponse(VPNResponse toCache);
void deleteResponse(String ip); void deleteResponse(String ip);
boolean isWhitelisted(UUID uuid); boolean isWhitelisted(UUID uuid);
boolean isWhitelisted(String cidr); boolean isWhitelisted(String cidr);
boolean isWhitelisted(CIDRUtils cidr); boolean isWhitelisted(CIDRUtils cidr);
void addWhitelist(UUID uuid); void addWhitelist(UUID uuid);
void removeWhitelist(UUID uuid); void removeWhitelist(UUID uuid);
void addWhitelist(CIDRUtils cidr); void addWhitelist(CIDRUtils cidr);
void removeWhitelist(CIDRUtils cidr); void removeWhitelist(CIDRUtils cidr);
List<UUID> getAllWhitelisted(); List<UUID> getAllWhitelisted();
List<CIDRUtils> getAllWhitelistedIps(); List<CIDRUtils> getAllWhitelistedIps();
void alertsState(UUID uuid, Consumer<Boolean> result); void alertsState(UUID uuid, Consumer<Boolean> result);
void updateAlertsState(UUID uuid, boolean state); void updateAlertsState(UUID uuid, boolean state);
void clearResponses(); void clearResponses();
void init(); void init();
void shutdown(); void shutdown();
} }
@@ -24,6 +24,8 @@ import dev.brighten.antivpn.database.sql.utils.Query;
import dev.brighten.antivpn.database.version.Version; import dev.brighten.antivpn.database.version.Version;
import dev.brighten.antivpn.utils.CIDRUtils; import dev.brighten.antivpn.utils.CIDRUtils;
import dev.brighten.antivpn.web.objects.VPNResponse; import dev.brighten.antivpn.web.objects.VPNResponse;
import lombok.SneakyThrows;
import java.io.File; import java.io.File;
import java.math.BigInteger; import java.math.BigInteger;
import java.net.UnknownHostException; import java.net.UnknownHostException;
@@ -33,421 +35,346 @@ import java.sql.Timestamp;
import java.util.*; import java.util.*;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.function.Consumer; import java.util.function.Consumer;
import lombok.SneakyThrows;
public class H2VPN implements VPNDatabase { public class H2VPN implements VPNDatabase {
public H2VPN() {
AntiVPN.getInstance()
.getExecutor()
.getThreadExecutor()
.scheduleAtFixedRate(
() -> {
if (!AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled() || MySQL.isClosed())
return;
// Refreshing whitelisted players public H2VPN() {
AntiVPN.getInstance().getExecutor().getWhitelisted().clear(); AntiVPN.getInstance().getExecutor().getThreadExecutor().scheduleAtFixedRate(() -> {
AntiVPN.getInstance() if(!AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled() || MySQL.isClosed()) return;
.getExecutor()
.getWhitelisted()
.addAll(AntiVPN.getInstance().getDatabase().getAllWhitelisted());
// Refreshing whitlisted IPs //Refreshing whitelisted players
AntiVPN.getInstance().getExecutor().getWhitelistedIps().clear(); AntiVPN.getInstance().getExecutor().getWhitelisted().clear();
AntiVPN.getInstance() AntiVPN.getInstance().getExecutor().getWhitelisted()
.getExecutor() .addAll(AntiVPN.getInstance().getDatabase().getAllWhitelisted());
.getWhitelistedIps()
.addAll(AntiVPN.getInstance().getDatabase().getAllWhitelistedIps());
},
2,
30,
TimeUnit.SECONDS);
}
@Override //Refreshing whitlisted IPs
public Optional<VPNResponse> getStoredResponse(String ip) { AntiVPN.getInstance().getExecutor().getWhitelistedIps().clear();
if (!AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled() || MySQL.isClosed()) AntiVPN.getInstance().getExecutor().getWhitelistedIps()
return Optional.empty(); .addAll(AntiVPN.getInstance().getDatabase().getAllWhitelistedIps());
}, 2, 30, TimeUnit.SECONDS);
try (ExecutableStatement statement =
Query.prepare("select * from `responses` where `ip` = ? limit 1").append(ip)) {
try (ResultSet rs = statement.executeQuery()) {
if (rs != null && rs.next()) {
return Optional.of(
new VPNResponse(
rs.getString("asn"),
rs.getString("ip"),
rs.getString("countryName"),
rs.getString("countryCode"),
rs.getString("city"),
rs.getString("timeZone"),
rs.getString("method"),
rs.getString("isp"),
"N/A",
rs.getBoolean("proxy"),
rs.getBoolean("cached"),
true,
rs.getDouble("latitude"),
rs.getDouble("longitude"),
rs.getTimestamp("inserted").getTime(),
-1));
}
}
} catch (SQLException e) {
AntiVPN.getInstance()
.getExecutor()
.logException("There was a problem getting a response for " + ip, e);
} catch (Exception e) {
throw new RuntimeException(e);
}
return Optional.empty();
}
/*
* Query.
* prepare("create table if not exists `responses` (`ip` varchar(45) not null, "
* +
* "`countryName` varchar(64), `countryCode` varchar(10), `city` varchar(64), `timeZone` varchar(64), "
* +
* "`method` varchar(32), `isp` varchar(32), `proxy` boolean, `cached` boolean "
* + "`latitude` double, `longitude` double)");
*/
@Override
public void cacheResponse(VPNResponse toCache) {
if (!AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled() || MySQL.isClosed()) return;
try (var statement =
Query.prepare(
"insert into `responses` (`ip`,`asn`,`countryName`,`countryCode`,`city`,`timeZone`,"
+ "`method`,`isp`,`proxy`,`cached`,`inserted`,`latitude`,`longitude`) values (?,?,?,?,?,?,?,?,?,?,?,?,?)")
.append(toCache.getIp())
.append(toCache.getAsn())
.append(toCache.getCountryName())
.append(toCache.getCountryCode())
.append(toCache.getCity())
.append(toCache.getTimeZone())
.append(toCache.getMethod())
.append(toCache.getIsp())
.append(toCache.isProxy())
.append(toCache.isCached())
.append(new Timestamp(System.currentTimeMillis()))
.append(toCache.getLatitude())
.append(toCache.getLongitude())) {
statement.execute();
} catch (SQLException e) {
AntiVPN.getInstance()
.getExecutor()
.logException("Could not cache response for IP: " + toCache.getIp(), e);
}
}
@Override
public void deleteResponse(String ip) {
if (!AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled() || MySQL.isClosed()) return;
try (var statement = Query.prepare("delete from `responses` where `ip` = ?").append(ip)) {
statement.execute();
} catch (SQLException e) {
AntiVPN.getInstance()
.getExecutor()
.logException("Could not delete response from IP: " + ip, e);
}
}
@Override
public boolean isWhitelisted(UUID uuid) {
if (!AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled() || MySQL.isClosed()) return false;
try (var statement =
Query.prepare("select uuid from `whitelisted` where `uuid` = ? limit 1")
.append(uuid.toString())) {
try (var set = statement.executeQuery()) {
return set != null && set.next() && set.getString("uuid") != null;
}
} catch (SQLException e) {
AntiVPN.getInstance()
.getExecutor()
.logException("Could not check whitelist for uuid '" + uuid + "' due to SQL error.", e);
return false;
}
}
@SneakyThrows
@Override
public boolean isWhitelisted(String cidr) {
return isWhitelisted(new CIDRUtils(cidr));
}
@Override
public boolean isWhitelisted(CIDRUtils cidr) {
if (!AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled() || MySQL.isClosed()) return false;
BigInteger start = cidr.getStartIpInt();
BigInteger end = cidr.getEndIpInt();
try (var statement =
Query.prepare("SELECT * FROM `whitelisted-ranges` WHERE ip_start <= ? AND ip_end >= ?")
.append(start)
.append(end)) {
try (var result = statement.executeQuery()) {
return result.next();
}
} catch (SQLException e) {
AntiVPN.getInstance()
.getExecutor()
.logException("Could not check whitelist for cidr '" + cidr + "' due to SQL error.", e);
}
return false;
}
@Override
public void addWhitelist(UUID uuid) {
if (!AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled() || MySQL.isClosed()) return;
try (var statement =
Query.prepare("insert into `whitelisted` (`uuid`) values (?)").append(uuid.toString())) {
statement.execute();
AntiVPN.getInstance().getExecutor().getWhitelisted().add(uuid);
} catch (SQLException e) {
AntiVPN.getInstance()
.getExecutor()
.logException("Could not add uuid '" + uuid + "' to whitelist due to SQL error.", e);
}
}
@Override
public void removeWhitelist(UUID uuid) {
if (!AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled() || MySQL.isClosed()) return;
try (var statement =
Query.prepare("delete from `whitelisted` where `uuid` = ?").append(uuid.toString())) {
statement.execute();
AntiVPN.getInstance().getExecutor().getWhitelisted().remove(uuid);
} catch (SQLException e) {
AntiVPN.getInstance()
.getExecutor()
.logException("Could not remove uuid '" + uuid + "' from whitelist due to SQL error.", e);
}
}
@Override
public void addWhitelist(CIDRUtils cidr) {
if (!AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled() || MySQL.isClosed()) return;
try (var statement =
Query.prepare(
"insert into `whitelisted-ranges` (`cidr_string`, `ip_start`, `ip_end`) values (?, ?, ?)")
.append(cidr.getCidr())
.append(cidr.getStartIpInt())
.append(cidr.getEndIpInt())) {
statement.execute();
} catch (SQLException e) {
AntiVPN.getInstance()
.getExecutor()
.logException("Could not add cidr '" + cidr + "' to whitelist due to SQL error.", e);
}
}
@Override
public void removeWhitelist(CIDRUtils cidr) {
if (!AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled() || MySQL.isClosed()) return;
try (var statement =
Query.prepare("delete from `whitelisted-ranges` where `cidr_string` = ?")
.append(cidr.getCidr())) {
statement.execute();
} catch (SQLException e) {
AntiVPN.getInstance()
.getExecutor()
.logException("Could not remove cidr '" + cidr + "' from whitelist due to SQL error.", e);
}
}
@Override
public List<UUID> getAllWhitelisted() {
List<UUID> uuids = new ArrayList<>();
if (!AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled() || MySQL.isClosed()) return uuids;
try (var statement = Query.prepare("select uuid from `whitelisted`")) {
statement.execute(set -> uuids.add(UUID.fromString(set.getString("uuid"))));
} catch (SQLException e) {
AntiVPN.getInstance()
.getExecutor()
.logException("Could not get all whitelisted players due to SQL error.", e);
} }
return uuids; @Override
} public Optional<VPNResponse> getStoredResponse(String ip) {
if (!AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled()|| MySQL.isClosed())
return Optional.empty();
@Override try(ExecutableStatement statement = Query.prepare("select * from `responses` where `ip` = ? limit 1").append(ip)) {
public List<CIDRUtils> getAllWhitelistedIps() { try(ResultSet rs = statement.executeQuery()) {
List<CIDRUtils> ips = new ArrayList<>(); if (rs != null && rs.next()) {
return Optional.of(new VPNResponse(rs.getString("asn"), rs.getString("ip"),
if (!AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled() || MySQL.isClosed()) return ips; rs.getString("countryName"), rs.getString("countryCode"),
try (var statement = rs.getString("city"), rs.getString("timeZone"),
Query.prepare("select `cidr_string`, `ip_start`, `ip_end` from `whitelisted-ranges`")) { rs.getString("method"), rs.getString("isp"), "N/A",
statement.execute( rs.getBoolean("proxy"), rs.getBoolean("cached"), true,
set -> { rs.getDouble("latitude"), rs.getDouble("longitude"),
try { rs.getTimestamp("inserted").getTime(), -1));
String cidrString = set.getString("cidr_string");
ips.add(new CIDRUtils(cidrString));
} catch (UnknownHostException e) {
AntiVPN.getInstance()
.getExecutor()
.logException(
"Could not format ip " + set.getString("cidr_string") + " into a CIDR!", e);
}
});
} catch (SQLException e) {
AntiVPN.getInstance()
.getExecutor()
.logException("Could not get all whitelisted ips due to SQL error.", e);
}
return ips;
}
@Override
public void alertsState(UUID uuid, Consumer<Boolean> result) {
if (MySQL.isClosed()) return;
AntiVPN.getInstance()
.getExecutor()
.getThreadExecutor()
.execute(
() -> {
try (var statement =
Query.prepare("select * from `alerts` where `uuid` = ? limit 1")
.append(uuid.toString())) {
try (var set = statement.executeQuery()) {
result.accept(set != null && set.next() && set.getString("uuid") != null);
} }
} catch (SQLException e) { }
AntiVPN.getInstance() } catch (SQLException e) {
.getExecutor() AntiVPN.getInstance().getExecutor().logException("There was a problem getting a response for "
.logException("There was a problem getting alerts state for " + uuid, e); + ip, e);
result.accept(false); } catch (Exception e) {
} throw new RuntimeException(e);
});
}
@Override
public void updateAlertsState(UUID uuid, boolean enabled) {
if (MySQL.isClosed()) return;
if (enabled) {
// We want to make sure there isn't already a uuid inserted to prevent double insertions
alertsState(
uuid,
alreadyEnabled -> { // No need to make another thread execute, already async
if (!alreadyEnabled) {
try (var statement =
Query.prepare("insert into `alerts` (`uuid`) values (?)")
.append(uuid.toString())) {
statement.execute();
} catch (SQLException e) {
AntiVPN.getInstance()
.getExecutor()
.logException("There was a problem updating alerts state for " + uuid, e);
}
} // No need to insert again of already enabled
});
// Removing any uuid from the alerts table will disable alerts globally.
} else {
try (var statement =
Query.prepare("delete from `alerts` where `uuid` = ?").append(uuid.toString())) {
statement.execute();
} catch (SQLException e) {
AntiVPN.getInstance()
.getExecutor()
.logException("There was a problem updating alerts state for " + uuid, e);
}
}
}
@Override
public void clearResponses() {
if (MySQL.isClosed()) return;
try (var statement = Query.prepare("delete from `responses`")) {
statement.execute();
} catch (SQLException e) {
AntiVPN.getInstance()
.getExecutor()
.logException("There was a problem clearing responses.", e);
}
}
@Override
public void init() {
if (!AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled()) return;
AntiVPN.getInstance().getExecutor().log("Initializing H2...");
MySQL.initH2();
try {
for (Version<H2VPN> version : Version.h2Versions) {
if (version.needsUpdate(this)) {
version.update(this);
} }
} return Optional.empty();
} catch (Exception e) {
throw new RuntimeException("Could not complete version setup due to SQL error", e);
} }
AntiVPN.getInstance().getExecutor().log("Creating tables..."); /*
* Query.
* prepare("create table if not exists `responses` (`ip` varchar(45) not null, "
* +
* "`countryName` varchar(64), `countryCode` varchar(10), `city` varchar(64), `timeZone` varchar(64), "
* +
* "`method` varchar(32), `isp` varchar(32), `proxy` boolean, `cached` boolean "
* + "`latitude` double, `longitude` double)");
*/
@Override
public void cacheResponse(VPNResponse toCache) {
if (!AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled() || MySQL.isClosed())
return;
// Running check for old table types to update try(var statement = Query.prepare("insert into `responses` (`ip`,`asn`,`countryName`,`countryCode`,`city`,`timeZone`,"
} + "`method`,`isp`,`proxy`,`cached`,`inserted`,`latitude`,`longitude`) values (?,?,?,?,?,?,?,?,?,?,?,?,?)")
.append(toCache.getIp()).append(toCache.getAsn()).append(toCache.getCountryName())
@Override .append(toCache.getCountryCode()).append(toCache.getCity()).append(toCache.getTimeZone())
public void shutdown() { .append(toCache.getMethod()).append(toCache.getIsp()).append(toCache.isProxy())
if (!AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled()) return; .append(toCache.isCached()).append(new Timestamp(System.currentTimeMillis()))
.append(toCache.getLatitude()).append(toCache.getLongitude())) {
MySQL.shutdown(); statement.execute();
} } catch(SQLException e) {
AntiVPN.getInstance().getExecutor().logException("Could not cache response for IP: " + toCache.getIp(), e);
public void backupDatabase() { }
File dataFolder = new File(AntiVPN.getInstance().getPluginFolder(), "databases");
if (!dataFolder.exists() || MySQL.isClosed()) {
return;
} }
try { @Override
var connection = Query.getConn(); public void deleteResponse(String ip) {
if (connection == null if(!AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled() || MySQL.isClosed())
|| connection.getMetaData() == null return;
|| !connection.getMetaData().getDatabaseProductName().equalsIgnoreCase("H2")) {
return; try(var statement = Query.prepare("delete from `responses` where `ip` = ?").append(ip)) {
} statement.execute();
} catch (SQLException e) { } catch (SQLException e) {
AntiVPN.getInstance() AntiVPN.getInstance().getExecutor().logException("Could not delete response from IP: " + ip, e);
.getExecutor() }
.logException("Could not verify database type before H2 backup.", e);
return;
} }
File backupDir = new File(dataFolder, "backups"); @Override
if (!backupDir.exists() && !backupDir.mkdirs()) { public boolean isWhitelisted(UUID uuid) {
AntiVPN.getInstance().getExecutor().log("Could not create backup directory"); if (!AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled() || MySQL.isClosed())
return; return false;
try(var statement = Query.prepare("select uuid from `whitelisted` where `uuid` = ? limit 1")
.append(uuid.toString())) {
try(var set = statement.executeQuery()) {
return set != null && set.next() && set.getString("uuid") != null;
}
} catch (SQLException e) {
AntiVPN.getInstance().getExecutor().logException("Could not check whitelist for uuid '" + uuid + "' due to SQL error.", e);
return false;
}
} }
File backupFile = @SneakyThrows
new File(backupDir, "database.h2_backup_" + System.currentTimeMillis() + ".zip"); @Override
String backupPath = backupFile.getAbsolutePath().replace("\\", "/").replace("'", "''"); public boolean isWhitelisted(String cidr) {
return isWhitelisted(new CIDRUtils(cidr));
try (var statement = Query.prepare("BACKUP TO '" + backupPath + "'")) { }
statement.execute();
AntiVPN.getInstance().getExecutor().log("Created H2 backup at " + backupFile.getName()); @Override
} catch (SQLException e) { public boolean isWhitelisted(CIDRUtils cidr) {
AntiVPN.getInstance() if (!AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled() || MySQL.isClosed())
.getExecutor() return false;
.logException("Could not create H2 backup before migration.", e);
BigInteger start = cidr.getStartIpInt();
BigInteger end = cidr.getEndIpInt();
try(var statement = Query.prepare("SELECT * FROM `whitelisted-ranges` WHERE ip_start <= ? AND ip_end >= ?")
.append(start).append(end)) {
try(var result = statement.executeQuery()) {
return result.next();
}
} catch (SQLException e) {
AntiVPN.getInstance().getExecutor().logException("Could not check whitelist for cidr '" + cidr + "' due to SQL error.", e);
}
return false;
}
@Override
public void addWhitelist(UUID uuid) {
if (!AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled() || MySQL.isClosed())
return;
try(var statement = Query.prepare("insert into `whitelisted` (`uuid`) values (?)").append(uuid.toString())) {
statement.execute();
AntiVPN.getInstance().getExecutor().getWhitelisted().add(uuid);
} catch (SQLException e) {
AntiVPN.getInstance().getExecutor().logException("Could not add uuid '" + uuid + "' to whitelist due to SQL error.", e);
}
}
@Override
public void removeWhitelist(UUID uuid) {
if (!AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled() || MySQL.isClosed())
return;
try(var statement = Query.prepare("delete from `whitelisted` where `uuid` = ?").append(uuid.toString())) {
statement.execute();
AntiVPN.getInstance().getExecutor().getWhitelisted().remove(uuid);
} catch (SQLException e) {
AntiVPN.getInstance().getExecutor().logException("Could not remove uuid '" + uuid + "' from whitelist due to SQL error.", e);
}
}
@Override
public void addWhitelist(CIDRUtils cidr) {
if (!AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled() || MySQL.isClosed())
return;
try(var statement = Query.prepare("insert into `whitelisted-ranges` (`cidr_string`, `ip_start`, `ip_end`) values (?, ?, ?)")
.append(cidr.getCidr()).append(cidr.getStartIpInt()).append(cidr.getEndIpInt())) {
statement.execute();
} catch (SQLException e) {
AntiVPN.getInstance().getExecutor().logException("Could not add cidr '" + cidr + "' to whitelist due to SQL error.", e);
}
}
@Override
public void removeWhitelist(CIDRUtils cidr) {
if (!AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled() || MySQL.isClosed())
return;
try(var statement = Query.prepare("delete from `whitelisted-ranges` where `cidr_string` = ?").append(cidr.getCidr())) {
statement.execute();
} catch (SQLException e) {
AntiVPN.getInstance().getExecutor().logException("Could not remove cidr '" + cidr + "' from whitelist due to SQL error.", e);
}
}
@Override
public List<UUID> getAllWhitelisted() {
List<UUID> uuids = new ArrayList<>();
if (!AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled() || MySQL.isClosed())
return uuids;
try(var statement = Query.prepare("select uuid from `whitelisted`")) {
statement.execute(set -> uuids.add(UUID.fromString(set.getString("uuid"))));
} catch (SQLException e) {
AntiVPN.getInstance().getExecutor().logException("Could not get all whitelisted players due to SQL error.", e);
}
return uuids;
}
@Override
public List<CIDRUtils> getAllWhitelistedIps() {
List<CIDRUtils> ips = new ArrayList<>();
if (!AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled() || MySQL.isClosed())
return ips;
try(var statement = Query.prepare("select `cidr_string`, `ip_start`, `ip_end` from `whitelisted-ranges`")) {
statement.execute(set -> {
try {
String cidrString = set.getString("cidr_string");
ips.add(new CIDRUtils(cidrString));
} catch (UnknownHostException e) {
AntiVPN.getInstance().getExecutor()
.logException("Could not format ip "
+ set.getString("cidr_string") + " into a CIDR!", e);
}
});
} catch (SQLException e) {
AntiVPN.getInstance().getExecutor().logException("Could not get all whitelisted ips due to SQL error.", e);
}
return ips;
}
@Override
public void alertsState(UUID uuid, Consumer<Boolean> result) {
if(MySQL.isClosed()) return;
AntiVPN.getInstance().getExecutor().getThreadExecutor().execute(() -> {
try(var statement = Query.prepare("select * from `alerts` where `uuid` = ? limit 1")
.append(uuid.toString())) {
try(var set = statement.executeQuery()) {
result.accept(set != null && set.next() && set.getString("uuid") != null);
}
} catch (SQLException e) {
AntiVPN.getInstance().getExecutor().logException("There was a problem getting alerts state for " + uuid, e);
result.accept(false);
}
});
}
@Override
public void updateAlertsState(UUID uuid, boolean enabled) {
if(MySQL.isClosed()) return;
if(enabled) {
//We want to make sure there isn't already a uuid inserted to prevent double insertions
alertsState(uuid, alreadyEnabled -> { //No need to make another thread execute, already async
if(!alreadyEnabled) {
try(var statement = Query.prepare("insert into `alerts` (`uuid`) values (?)")
.append(uuid.toString())) {
statement.execute();
} catch (SQLException e) {
AntiVPN.getInstance().getExecutor()
.logException("There was a problem updating alerts state for " + uuid, e);
}
} //No need to insert again of already enabled
});
//Removing any uuid from the alerts table will disable alerts globally.
} else {
try(var statement = Query.prepare("delete from `alerts` where `uuid` = ?").append(uuid.toString())) {
statement.execute();
} catch (SQLException e) {
AntiVPN.getInstance().getExecutor().logException("There was a problem updating alerts state for "
+ uuid, e);
}
}
}
@Override
public void clearResponses() {
if(MySQL.isClosed()) return;
try(var statement = Query.prepare("delete from `responses`")) {
statement.execute();
} catch (SQLException e) {
AntiVPN.getInstance().getExecutor().logException("There was a problem clearing responses.", e);
}
}
@Override
public void init() {
if (!AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled())
return;
AntiVPN.getInstance().getExecutor().log("Initializing H2...");
MySQL.initH2();
try {
for (Version<H2VPN> version : Version.h2Versions) {
if(version.needsUpdate(this)) {
version.update(this);
}
}
} catch (Exception e) {
throw new RuntimeException("Could not complete version setup due to SQL error", e);
}
AntiVPN.getInstance().getExecutor().log("Creating tables...");
//Running check for old table types to update
}
@Override
public void shutdown() {
if (!AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled())
return;
MySQL.shutdown();
}
public void backupDatabase() {
File dataFolder = new File(AntiVPN.getInstance().getPluginFolder(), "databases");
if(!dataFolder.exists() || MySQL.isClosed()) {
return;
}
try {
var connection = Query.getConn();
if (connection == null || connection.getMetaData() == null
|| !connection.getMetaData().getDatabaseProductName().equalsIgnoreCase("H2")) {
return;
}
} catch (SQLException e) {
AntiVPN.getInstance().getExecutor().logException("Could not verify database type before H2 backup.", e);
return;
}
File backupDir = new File(dataFolder, "backups");
if (!backupDir.exists() && !backupDir.mkdirs()) {
AntiVPN.getInstance().getExecutor().log("Could not create backup directory");
return;
}
File backupFile = new File(backupDir, "database.h2_backup_" + System.currentTimeMillis() + ".zip");
String backupPath = backupFile.getAbsolutePath()
.replace("\\", "/")
.replace("'", "''");
try (var statement = Query.prepare("BACKUP TO '" + backupPath + "'")) {
statement.execute();
AntiVPN.getInstance().getExecutor().log("Created H2 backup at " + backupFile.getName());
} catch (SQLException e) {
AntiVPN.getInstance().getExecutor().logException("Could not create H2 backup before migration.", e);
}
} }
}
} }
@@ -23,6 +23,7 @@ import dev.brighten.antivpn.database.sql.utils.ExecutableStatement;
import dev.brighten.antivpn.database.sql.utils.Query; import dev.brighten.antivpn.database.sql.utils.Query;
import dev.brighten.antivpn.database.version.Version; import dev.brighten.antivpn.database.version.Version;
import dev.brighten.antivpn.utils.MiscUtils; import dev.brighten.antivpn.utils.MiscUtils;
import java.sql.DatabaseMetaData; import java.sql.DatabaseMetaData;
import java.sql.ResultSet; import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
@@ -31,104 +32,99 @@ import java.util.List;
public class First implements Version<VPNDatabase> { public class First implements Version<VPNDatabase> {
private final List<AutoCloseable> toClose = new ArrayList<>(); private final List<AutoCloseable> toClose = new ArrayList<>();
@Override
public void update(VPNDatabase database) throws DatabaseException {
try {
closeOnEnd(Query.prepare("create table if not exists `whitelisted` (`uuid` varchar(36) not null)"))
.execute();
closeOnEnd(Query.prepare("create table if not exists `whitelisted-ips` (`ip` varchar(45) not null)"))
.execute();
closeOnEnd(Query
.prepare("create table if not exists `responses` (`ip` varchar(45) not null, `asn` varchar(12),"
+ "`countryName` text, `countryCode` varchar(10), `city` text, `timeZone` varchar(64), "
+ "`method` varchar(32), `isp` text, `proxy` boolean, `cached` boolean, `inserted` timestamp,"
+ "`latitude` double, `longitude` double)")).execute();
closeOnEnd(Query.prepare("create table if not exists `alerts` (`uuid` varchar(36) not null)"))
.execute();
closeOnEnd(Query.prepare("create table if not exists `database_version` (`version` int)")).execute();
closeOnEnd(Query.prepare("insert into `database_version` (`version`) values (?)")
.append(versionNumber())).execute();
@Override AntiVPN.getInstance().getExecutor().log("Creating indexes...");
public void update(VPNDatabase database) throws DatabaseException { createIndexIfAbsent("whitelisted", "whitelisted_uuid_1", "`uuid`");
try { createIndexIfAbsent("responses", "responses_ip_1", "`ip`");
closeOnEnd( createIndexIfAbsent("responses", "responses_proxy_1", "`proxy`");
Query.prepare( createIndexIfAbsent("responses", "responses_inserted_1", "`inserted`");
"create table if not exists `whitelisted` (`uuid` varchar(36) not null)")) createIndexIfAbsent("whitelisted-ips", "whitelisted_ips_ip_1", "`ip`");
.execute(); } catch (SQLException e) {
closeOnEnd( throw new DatabaseException("Failed to update database", e);
Query.prepare( } finally {
"create table if not exists `whitelisted-ips` (`ip` varchar(45) not null)")) MiscUtils.close(toClose.toArray(AutoCloseable[]::new));
.execute(); toClose.clear();
closeOnEnd(
Query.prepare(
"create table if not exists `responses` (`ip` varchar(45) not null, `asn` varchar(12),"
+ "`countryName` text, `countryCode` varchar(10), `city` text, `timeZone` varchar(64), "
+ "`method` varchar(32), `isp` text, `proxy` boolean, `cached` boolean, `inserted` timestamp,"
+ "`latitude` double, `longitude` double)"))
.execute();
closeOnEnd(Query.prepare("create table if not exists `alerts` (`uuid` varchar(36) not null)"))
.execute();
closeOnEnd(Query.prepare("create table if not exists `database_version` (`version` int)"))
.execute();
closeOnEnd(
Query.prepare("insert into `database_version` (`version`) values (?)")
.append(versionNumber()))
.execute();
AntiVPN.getInstance().getExecutor().log("Creating indexes...");
createIndexIfAbsent("whitelisted", "whitelisted_uuid_1", "`uuid`");
createIndexIfAbsent("responses", "responses_ip_1", "`ip`");
createIndexIfAbsent("responses", "responses_proxy_1", "`proxy`");
createIndexIfAbsent("responses", "responses_inserted_1", "`inserted`");
createIndexIfAbsent("whitelisted-ips", "whitelisted_ips_ip_1", "`ip`");
} catch (SQLException e) {
throw new DatabaseException("Failed to update database", e);
} finally {
MiscUtils.close(toClose.toArray(AutoCloseable[]::new));
toClose.clear();
}
}
private ExecutableStatement closeOnEnd(ExecutableStatement statement) {
toClose.add(statement);
return statement;
}
protected void createIndexIfAbsent(String tableName, String indexName, String columnList)
throws SQLException {
if (hasIndex(tableName, indexName)) {
return;
}
closeOnEnd(
Query.prepare(
String.format("create index `%s` on `%s` (%s)", indexName, tableName, columnList)))
.execute();
}
protected void dropIndexIfPresent(String tableName, String indexName) throws SQLException {
if (!hasIndex(tableName, indexName)) {
return;
}
closeOnEnd(Query.prepare(String.format("drop index `%s` on `%s`", indexName, tableName)))
.execute();
}
protected boolean hasIndex(String tableName, String indexName) throws SQLException {
DatabaseMetaData metaData = Query.getConn().getMetaData();
try (ResultSet indexes = metaData.getIndexInfo(null, null, tableName, false, false)) {
while (indexes.next()) {
String existingIndexName = indexes.getString("INDEX_NAME");
if (existingIndexName != null && existingIndexName.equalsIgnoreCase(indexName)) {
return true;
} }
}
} }
return false; private ExecutableStatement closeOnEnd(ExecutableStatement statement) {
} toClose.add(statement);
return statement;
@Override }
public int versionNumber() {
return 0; protected void createIndexIfAbsent(String tableName, String indexName, String columnList) throws SQLException {
} if (hasIndex(tableName, indexName)) {
return;
@Override }
public boolean needsUpdate(VPNDatabase database) {
try (var statement = Query.prepare("select * from `database_version` where version = 0")) { closeOnEnd(Query.prepare(String.format(
try (ResultSet set = statement.executeQuery()) { "create index `%s` on `%s` (%s)",
return !set.next(); indexName,
} tableName,
} catch (SQLException e) { columnList
return true; ))).execute();
}
protected void dropIndexIfPresent(String tableName, String indexName) throws SQLException {
if (!hasIndex(tableName, indexName)) {
return;
}
closeOnEnd(Query.prepare(String.format(
"drop index `%s` on `%s`",
indexName,
tableName
))).execute();
}
protected boolean hasIndex(String tableName, String indexName) throws SQLException {
DatabaseMetaData metaData = Query.getConn().getMetaData();
try (ResultSet indexes = metaData.getIndexInfo(null, null, tableName, false, false)) {
while (indexes.next()) {
String existingIndexName = indexes.getString("INDEX_NAME");
if (existingIndexName != null && existingIndexName.equalsIgnoreCase(indexName)) {
return true;
}
}
}
return false;
}
@Override
public int versionNumber() {
return 0;
}
@Override
public boolean needsUpdate(VPNDatabase database) {
try(var statement = Query.prepare("select * from `database_version` where version = 0")) {
try(ResultSet set = statement.executeQuery()) {
return !set.next();
}
} catch (SQLException e) {
return true;
}
} }
}
} }
@@ -26,148 +26,133 @@ import dev.brighten.antivpn.database.sql.utils.Query;
import dev.brighten.antivpn.database.version.Version; import dev.brighten.antivpn.database.version.Version;
import dev.brighten.antivpn.utils.CIDRUtils; import dev.brighten.antivpn.utils.CIDRUtils;
import dev.brighten.antivpn.utils.MiscUtils; import dev.brighten.antivpn.utils.MiscUtils;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
public class Second extends First implements Version<VPNDatabase> { public class Second extends First implements Version<VPNDatabase> {
private final List<AutoCloseable> toClose = new ArrayList<>(); private final List<AutoCloseable> toClose = new ArrayList<>();
@Override @Override
public void update(VPNDatabase database) throws DatabaseException { public void update(VPNDatabase database) throws DatabaseException {
if (database instanceof H2VPN h2VPN && !(database instanceof MySqlVPN)) { if(database instanceof H2VPN h2VPN && !(database instanceof MySqlVPN)) {
h2VPN.backupDatabase(); h2VPN.backupDatabase();
}
List<String> whitelistedIps = new ArrayList<>();
try (var statement = Query.prepare("SELECT * FROM `whitelisted-ips`")) {
try (var set = statement.executeQuery()) {
while (set.next()) {
whitelistedIps.add(set.getString("ip"));
} }
} List<String> whitelistedIps = new ArrayList<>();
} catch (SQLException e) {
throw new DatabaseException("Could not get whitelisted ips from database!", e);
}
try { try (var statement = Query.prepare("SELECT * FROM `whitelisted-ips`")) {
closeOnEnd( try(var set = statement.executeQuery()) {
Query.prepare( while (set.next()) {
"CREATE TABLE IF NOT EXISTS `whitelisted-ranges` " whitelistedIps.add(set.getString("ip"));
+ "(id INT AUTO_INCREMENT PRIMARY KEY, " }
+ "cidr_string VARCHAR(45), " }
+ "ip_start BIGINT NOT NULL, " } catch (SQLException e) {
+ "ip_end BIGINT NOT NULL)")) throw new DatabaseException("Could not get whitelisted ips from database!", e);
.execute();
createIndexIfAbsent("whitelisted-ranges", "idx_ip_range", "ip_start, ip_end");
var cidrs =
whitelistedIps.stream()
.map(
ip -> {
try {
return new CIDRUtils(ip + "/32");
} catch (UnknownHostException e) {
throw new RuntimeException("Could not format ip " + ip + " into a CIDR!", e);
}
})
.toList();
var insertStatement =
Query.prepare(
"INSERT INTO `whitelisted-ranges` (`cidr_string`, `ip_start`, `ip_end`) VALUES (?, ?, ?)");
for (CIDRUtils cidr : cidrs) {
insertStatement =
insertStatement
.append(cidr.toString())
.append(cidr.getStartIpInt())
.append(cidr.getEndIpInt())
.addBatch();
}
int[] updateCounts = insertStatement.executeBatch();
for (int updateCount : updateCounts) {
if (updateCount == 0) {
throw new RuntimeException(
"Could not insert a CIDR from previous whitelisted lists, attempted to restore previous database!");
} }
}
dropIndexIfPresent("whitelisted-ips", "ip_1"); try {
dropIndexIfPresent("whitelisted-ips", "whitelisted_ips_ip_1"); closeOnEnd(Query.prepare("CREATE TABLE IF NOT EXISTS `whitelisted-ranges` " +
closeOnEnd(Query.prepare("DROP TABLE `whitelisted-ips`")).execute(); "(id INT AUTO_INCREMENT PRIMARY KEY, " +
closeOnEnd( "cidr_string VARCHAR(45), " +
Query.prepare("INSERT INTO `database_version` (`version`) VALUES (?)") "ip_start BIGINT NOT NULL, " +
.append(versionNumber())) "ip_end BIGINT NOT NULL)"))
.execute(); .execute();
} catch (Throwable e) { createIndexIfAbsent("whitelisted-ranges", "idx_ip_range", "ip_start, ip_end");
AntiVPN.getInstance()
.getExecutor()
.log("Failed to update database to version 1: " + e.getMessage());
try {
rollback(whitelistedIps);
} catch (SQLException ex) {
throw new DatabaseException("Failed to rollback database!", e);
}
throw new DatabaseException("Failed to update to version one, rolling back database!", e);
} finally {
MiscUtils.close(toClose.toArray(AutoCloseable[]::new));
toClose.clear();
}
}
private ExecutableStatement closeOnEnd(ExecutableStatement statement) { var cidrs = whitelistedIps.stream().map(ip -> {
toClose.add(statement); try {
return statement; return new CIDRUtils(ip + "/32");
} } catch (UnknownHostException e) {
throw new RuntimeException("Could not format ip " + ip + " into a CIDR!", e);
}
}).toList();
var insertStatement = Query.prepare("INSERT INTO `whitelisted-ranges` (`cidr_string`, `ip_start`, `ip_end`) VALUES (?, ?, ?)");
for (CIDRUtils cidr : cidrs) {
insertStatement = insertStatement
.append(cidr.toString())
.append(cidr.getStartIpInt())
.append(cidr.getEndIpInt())
.addBatch();
}
int[] updateCounts = insertStatement.executeBatch();
for (int updateCount : updateCounts) {
if(updateCount == 0) {
throw new RuntimeException("Could not insert a CIDR from previous whitelisted lists, attempted to restore previous database!");
}
}
dropIndexIfPresent("whitelisted-ips", "ip_1");
dropIndexIfPresent("whitelisted-ips", "whitelisted_ips_ip_1");
closeOnEnd(Query.prepare("DROP TABLE `whitelisted-ips`")).execute();
closeOnEnd(Query.prepare("INSERT INTO `database_version` (`version`) VALUES (?)").append(versionNumber())).execute();
} catch (Throwable e) {
AntiVPN.getInstance().getExecutor().log("Failed to update database to version 1: " + e.getMessage());
try {
rollback(whitelistedIps);
} catch (SQLException ex) {
throw new DatabaseException("Failed to rollback database!", e);
}
throw new DatabaseException("Failed to update to version one, rolling back database!", e);
} finally {
MiscUtils.close(toClose.toArray(AutoCloseable[]::new));
toClose.clear();
}
private void rollback(List<String> ipAddresses) throws SQLException {
AntiVPN.getInstance().getExecutor().log("Rolling back to version 0...");
dropIndexIfPresent("whitelisted-ranges", "idx_ip_range");
try (var statement = Query.prepare("DROP TABLE `whitelisted-ranges`")) {
statement.execute();
} }
try (var statement = private ExecutableStatement closeOnEnd(ExecutableStatement statement) {
Query.prepare("DELETE FROM `database_version` WHERE version = ?").append(versionNumber())) { toClose.add(statement);
statement.execute(); return statement;
} }
try (var statement = private void rollback(List<String> ipAddresses) throws SQLException {
Query.prepare("CREATE TABLE IF NOT EXISTS `whitelisted-ips` (`ip` VARCHAR(45) NOT NULL)")) { AntiVPN.getInstance().getExecutor().log("Rolling back to version 0...");
statement.execute(); dropIndexIfPresent("whitelisted-ranges", "idx_ip_range");
try(var statement = Query.prepare("DROP TABLE `whitelisted-ranges`")) {
statement.execute();
}
try(var statement = Query.prepare("DELETE FROM `database_version` WHERE version = ?").append(versionNumber())) {
statement.execute();
}
try(var statement = Query.prepare("CREATE TABLE IF NOT EXISTS `whitelisted-ips` (`ip` VARCHAR(45) NOT NULL)")) {
statement.execute();
}
createIndexIfAbsent("whitelisted-ips", "whitelisted_ips_ip_1", "`ip`");
try(var statement = Query.prepare("DELETE FROM `whitelisted-ips`")) {
statement.execute();
}
try(var statement = Query.prepare("INSERT INTO `whitelisted-ips` (`ip`) VALUES (?)")) {
for (String ip : ipAddresses) {
statement.append(ip);
statement.addBatch();
}
statement.executeBatch();
}
} }
createIndexIfAbsent("whitelisted-ips", "whitelisted_ips_ip_1", "`ip`"); @Override
public int versionNumber() {
try (var statement = Query.prepare("DELETE FROM `whitelisted-ips`")) { return 1;
statement.execute();
} }
try (var statement = Query.prepare("INSERT INTO `whitelisted-ips` (`ip`) VALUES (?)")) { @Override
for (String ip : ipAddresses) { public boolean needsUpdate(VPNDatabase database) {
statement.append(ip); try (var statement = Query.prepare("select * from `database_version` where version = 1")) {
statement.addBatch(); try(var set = statement.executeQuery()) {
} return !set.next();
}
statement.executeBatch(); } catch (SQLException e) {
return true;
}
} }
}
@Override
public int versionNumber() {
return 1;
}
@Override
public boolean needsUpdate(VPNDatabase database) {
try (var statement = Query.prepare("select * from `database_version` where version = 1")) {
try (var set = statement.executeQuery()) {
return !set.next();
}
} catch (SQLException e) {
return true;
}
}
} }
@@ -23,6 +23,7 @@ import dev.brighten.antivpn.database.sql.utils.Query;
import dev.brighten.antivpn.database.version.Version; import dev.brighten.antivpn.database.version.Version;
import dev.brighten.antivpn.utils.CIDRUtils; import dev.brighten.antivpn.utils.CIDRUtils;
import dev.brighten.antivpn.utils.MiscUtils; import dev.brighten.antivpn.utils.MiscUtils;
import java.math.BigInteger; import java.math.BigInteger;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import java.sql.SQLException; import java.sql.SQLException;
@@ -31,132 +32,84 @@ import java.util.List;
import java.util.logging.Level; import java.util.logging.Level;
public class Third implements Version<VPNDatabase> { public class Third implements Version<VPNDatabase> {
@Override @Override
public void update(VPNDatabase database) throws DatabaseException { public void update(VPNDatabase database) throws DatabaseException {
List<CIDRUtils> ipRanges = new ArrayList<>(); List<CIDRUtils> ipRanges = new ArrayList<>();
List<CIDRUtils> rangesToInsert = new ArrayList<>(); List<CIDRUtils> rangesToInsert = new ArrayList<>();
List<BigInteger[]> rangesToRemove = new ArrayList<>(); List<BigInteger[]> rangesToRemove = new ArrayList<>();
try (var preparedQuery = Query.prepare("select ip_start, ip_end from `whitelisted-ranges`")) { try (var preparedQuery = Query.prepare("select ip_start, ip_end from `whitelisted-ranges`")) {
preparedQuery.execute( preparedQuery.execute(set -> {
set -> { BigInteger start = set.getBigDecimal("ip_start").toBigInteger();
BigInteger start = set.getBigDecimal("ip_start").toBigInteger(); BigInteger end = set.getBigDecimal("ip_end").toBigInteger();
BigInteger end = set.getBigDecimal("ip_end").toBigInteger();
try { try {
var range = MiscUtils.rangeToCidrs(start, end); var range = MiscUtils.rangeToCidrs(start, end);
if (range.size() > 1) { if(range.size() > 1) {
rangesToRemove.add(new BigInteger[] {start, end}); rangesToRemove.add(new BigInteger[]{start, end});
rangesToInsert.addAll(range); rangesToInsert.addAll(range);
AntiVPN.getInstance() AntiVPN.getInstance().getExecutor().log(Level.WARNING, "Found multiple CIDR ranges for whitelist range for %s, %s!", start, end);
.getExecutor() } else ipRanges.addAll(range);
.log( } catch (UnknownHostException e) {
Level.WARNING, AntiVPN.getInstance().getExecutor().logException(
"Found multiple CIDR ranges for whitelist range for %s, %s!", String.format("Could not convert ip range to CIDR! %s, %s", start, end), e);
start, }
end); });
} else ipRanges.addAll(range); } catch (SQLException e) {
} catch (UnknownHostException e) { AntiVPN.getInstance().getExecutor().logException("Could not get all whitelisted ranges due to SQL error.", e);
AntiVPN.getInstance() }
.getExecutor()
.logException( AntiVPN.getInstance().getExecutor().log("Inserting %s new ranges into database...", rangesToInsert.size());
String.format("Could not convert ip range to CIDR! %s, %s", start, end), e);
for (CIDRUtils cidr : rangesToInsert) {
try(var statement = Query.prepare("insert into `whitelisted-ranges` (`cidr_string`, `ip_start`, `ip_end`) values (?, ?, ?)")
.append(cidr.getCidr()).append(cidr.getStartIpInt()).append(cidr.getEndIpInt())) {
statement.execute();
} catch (SQLException e) {
AntiVPN.getInstance().getExecutor().logException("Could not add cidr '" + cidr + "' to whitelist due to SQL error.", e);
} }
}); }
} catch (SQLException e) {
AntiVPN.getInstance() AntiVPN.getInstance().getExecutor().log("Removing %s old ranges from database...", rangesToRemove.size());
.getExecutor()
.logException("Could not get all whitelisted ranges due to SQL error.", e); for (BigInteger[] range : rangesToRemove) {
try(var statement = Query.prepare("delete from `whitelisted-ranges` where `ip_start` = ? and `ip_end` = ?")) {
statement.append(range[0]).append(range[1]).execute();
} catch (SQLException e) {
AntiVPN.getInstance().getExecutor().logException("Could not remove cidr range '" + range[0] + ", " + range[1] + "' from whitelist due to SQL error.", e);
}
}
AntiVPN.getInstance().getExecutor().log("Updating %s ranges to proper CIDR notation with the database", ipRanges.size());
for (CIDRUtils cidr : ipRanges) {
try(var statement = Query.prepare("update `whitelisted-ranges` set `cidr_string` = ? where `ip_start` = ? and `ip_end` = ?")) {
statement.append(cidr.getCidr()).append(cidr.getStartIpInt()).append(cidr.getEndIpInt()).execute();
} catch (SQLException e) {
AntiVPN.getInstance().getExecutor().logException("Could not update cidr '" + cidr + "' to proper CIDR notation in whitelist due to SQL error.", e);
}
}
try (var preparedStatement = Query.prepare("INSERT INTO `database_version` (`version`) VALUES (?)").append(versionNumber())) {
preparedStatement.execute();
} catch (SQLException e) {
AntiVPN.getInstance().getExecutor().logException("Could not update database version to 2 due to SQL error.", e);
}
} }
AntiVPN.getInstance() @Override
.getExecutor() public int versionNumber() {
.log("Inserting %s new ranges into database...", rangesToInsert.size()); return 2;
for (CIDRUtils cidr : rangesToInsert) {
try (var statement =
Query.prepare(
"insert into `whitelisted-ranges` (`cidr_string`, `ip_start`, `ip_end`) values (?, ?, ?)")
.append(cidr.getCidr())
.append(cidr.getStartIpInt())
.append(cidr.getEndIpInt())) {
statement.execute();
} catch (SQLException e) {
AntiVPN.getInstance()
.getExecutor()
.logException("Could not add cidr '" + cidr + "' to whitelist due to SQL error.", e);
}
} }
AntiVPN.getInstance() @Override
.getExecutor() public boolean needsUpdate(VPNDatabase database) {
.log("Removing %s old ranges from database...", rangesToRemove.size()); try (var statement = Query.prepare("select * from `database_version` where version = 2")) {
try(var set = statement.executeQuery()) {
for (BigInteger[] range : rangesToRemove) { return !set.next();
try (var statement = }
Query.prepare("delete from `whitelisted-ranges` where `ip_start` = ? and `ip_end` = ?")) { } catch (SQLException e) {
statement.append(range[0]).append(range[1]).execute(); return true;
} catch (SQLException e) { }
AntiVPN.getInstance()
.getExecutor()
.logException(
"Could not remove cidr range '"
+ range[0]
+ ", "
+ range[1]
+ "' from whitelist due to SQL error.",
e);
}
} }
AntiVPN.getInstance()
.getExecutor()
.log("Updating %s ranges to proper CIDR notation with the database", ipRanges.size());
for (CIDRUtils cidr : ipRanges) {
try (var statement =
Query.prepare(
"update `whitelisted-ranges` set `cidr_string` = ? where `ip_start` = ? and `ip_end` = ?")) {
statement
.append(cidr.getCidr())
.append(cidr.getStartIpInt())
.append(cidr.getEndIpInt())
.execute();
} catch (SQLException e) {
AntiVPN.getInstance()
.getExecutor()
.logException(
"Could not update cidr '"
+ cidr
+ "' to proper CIDR notation in whitelist due to SQL error.",
e);
}
}
try (var preparedStatement =
Query.prepare("INSERT INTO `database_version` (`version`) VALUES (?)")
.append(versionNumber())) {
preparedStatement.execute();
} catch (SQLException e) {
AntiVPN.getInstance()
.getExecutor()
.logException("Could not update database version to 2 due to SQL error.", e);
}
}
@Override
public int versionNumber() {
return 2;
}
@Override
public boolean needsUpdate(VPNDatabase database) {
try (var statement = Query.prepare("select * from `database_version` where version = 2")) {
try (var set = statement.executeQuery()) {
return !set.next();
}
} catch (SQLException e) {
return true;
}
}
} }
@@ -16,6 +16,8 @@
package dev.brighten.antivpn.database.mongo; package dev.brighten.antivpn.database.mongo;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.mongodb.*; import com.mongodb.*;
import com.mongodb.client.MongoClient; import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoClients; import com.mongodb.client.MongoClients;
@@ -28,305 +30,251 @@ import dev.brighten.antivpn.database.VPNDatabase;
import dev.brighten.antivpn.database.version.Version; import dev.brighten.antivpn.database.version.Version;
import dev.brighten.antivpn.utils.CIDRUtils; import dev.brighten.antivpn.utils.CIDRUtils;
import dev.brighten.antivpn.web.objects.VPNResponse; import dev.brighten.antivpn.web.objects.VPNResponse;
import org.bson.Document;
import org.bson.conversions.Bson;
import org.bson.types.Decimal128;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import java.util.*; import java.util.*;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.function.Consumer; import java.util.function.Consumer;
import org.bson.Document;
import org.bson.conversions.Bson;
import org.bson.types.Decimal128;
public class MongoVPN implements VPNDatabase { public class MongoVPN implements VPNDatabase {
public MongoCollection<Document> settingsDocument; public MongoCollection<Document> settingsDocument;
MongoCollection<Document> cacheDocument; MongoCollection<Document> cacheDocument;
private MongoClient client; private MongoClient client;
public MongoDatabase antivpnDatabase; public MongoDatabase antivpnDatabase;
public MongoVPN() { public MongoVPN() {
AntiVPN.getInstance() AntiVPN.getInstance().getExecutor().getThreadExecutor().scheduleAtFixedRate(() -> {
.getExecutor() if(!AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled()) return;
.getThreadExecutor()
.scheduleAtFixedRate(
() -> {
if (!AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled()) return;
// Refreshing whitelisted players //Refreshing whitelisted players
AntiVPN.getInstance().getExecutor().getWhitelisted().clear(); AntiVPN.getInstance().getExecutor().getWhitelisted().clear();
AntiVPN.getInstance() AntiVPN.getInstance().getExecutor().getWhitelisted()
.getExecutor() .addAll(AntiVPN.getInstance().getDatabase().getAllWhitelisted());
.getWhitelisted()
.addAll(AntiVPN.getInstance().getDatabase().getAllWhitelisted());
// Refreshing whitlisted IPs //Refreshing whitlisted IPs
AntiVPN.getInstance().getExecutor().getWhitelistedIps().clear(); AntiVPN.getInstance().getExecutor().getWhitelistedIps().clear();
AntiVPN.getInstance() AntiVPN.getInstance().getExecutor().getWhitelistedIps()
.getExecutor() .addAll(AntiVPN.getInstance().getDatabase().getAllWhitelistedIps());
.getWhitelistedIps() }, 2, 30, TimeUnit.SECONDS);
.addAll(AntiVPN.getInstance().getDatabase().getAllWhitelistedIps());
},
2,
30,
TimeUnit.SECONDS);
}
@Override
public Optional<VPNResponse> getStoredResponse(String ip) {
Document rdoc = cacheDocument.find(Filters.eq("ip", ip)).first();
if (rdoc != null) {
long lastUpdate = rdoc.get("lastAccess", 0L);
if (System.currentTimeMillis() - lastUpdate > TimeUnit.HOURS.toMillis(1)) {
AntiVPN.getInstance().getExecutor().getThreadExecutor().execute(() -> deleteResponse(ip));
return null;
}
return Optional.of(
VPNResponse.builder()
.asn(rdoc.getString("asn"))
.ip(ip)
.countryName(rdoc.getString("countryName"))
.countryCode(rdoc.getString("countryCode"))
.city(rdoc.getString("city"))
.isp(rdoc.getString("isp"))
.method(rdoc.getString("method"))
.timeZone(rdoc.getString("timeZone"))
.proxy(rdoc.getBoolean("proxy"))
.cached(rdoc.getBoolean("cached"))
.success(true)
.latitude(rdoc.getDouble("latitude"))
.longitude(rdoc.getDouble("longitude"))
.lastAccess(rdoc.get("lastAccess", 0L))
.build());
} }
return Optional.empty(); @Override
} public Optional<VPNResponse> getStoredResponse(String ip) {
Document rdoc = cacheDocument.find(Filters.eq("ip", ip)).first();
@Override if(rdoc != null) {
public void cacheResponse(VPNResponse toCache) { long lastUpdate = rdoc.get("lastAccess", 0L);
if (AntiVPN.getInstance().getVpnConfig().cachedResults()) {
Document rdoc = new Document("ip", toCache.getIp());
rdoc.put("asn", toCache.getAsn()); if(System.currentTimeMillis() - lastUpdate > TimeUnit.HOURS.toMillis(1)) {
rdoc.put("countryName", toCache.getCountryName()); AntiVPN.getInstance().getExecutor().getThreadExecutor().execute(() -> deleteResponse(ip));
rdoc.put("countryCode", toCache.getCountryCode()); return null;
rdoc.put("city", toCache.getCity()); }
rdoc.put("isp", toCache.getIsp());
rdoc.put("method", toCache.getMethod());
rdoc.put("timeZone", toCache.getTimeZone());
rdoc.put("proxy", toCache.isProxy());
rdoc.put("cached", toCache.isCached());
rdoc.put("success", toCache.isSuccess());
rdoc.put("latitude", toCache.getLatitude());
rdoc.put("longitude", toCache.getLongitude());
rdoc.put("lastAccess", System.currentTimeMillis());
AntiVPN.getInstance() return Optional.of(VPNResponse.builder().asn(rdoc.getString("asn")).ip(ip)
.getExecutor() .countryName(rdoc.getString("countryName"))
.getThreadExecutor() .countryCode(rdoc.getString("countryCode"))
.execute( .city(rdoc.getString("city"))
() -> { .isp(rdoc.getString("isp"))
.method(rdoc.getString("method"))
.timeZone(rdoc.getString("timeZone"))
.proxy(rdoc.getBoolean("proxy"))
.cached(rdoc.getBoolean("cached"))
.success(true)
.latitude(rdoc.getDouble("latitude"))
.longitude(rdoc.getDouble("longitude"))
.lastAccess(rdoc.get("lastAccess", 0L))
.build());
}
return Optional.empty();
}
@Override
public void cacheResponse(VPNResponse toCache) {
if(AntiVPN.getInstance().getVpnConfig().cachedResults()) {
Document rdoc = new Document("ip", toCache.getIp());
rdoc.put("asn", toCache.getAsn());
rdoc.put("countryName", toCache.getCountryName());
rdoc.put("countryCode", toCache.getCountryCode());
rdoc.put("city", toCache.getCity());
rdoc.put("isp", toCache.getIsp());
rdoc.put("method", toCache.getMethod());
rdoc.put("timeZone", toCache.getTimeZone());
rdoc.put("proxy", toCache.isProxy());
rdoc.put("cached", toCache.isCached());
rdoc.put("success", toCache.isSuccess());
rdoc.put("latitude", toCache.getLatitude());
rdoc.put("longitude", toCache.getLongitude());
rdoc.put("lastAccess", System.currentTimeMillis());
AntiVPN.getInstance().getExecutor().getThreadExecutor().execute(() -> {
Bson update = new Document("$set", rdoc); Bson update = new Document("$set", rdoc);
cacheDocument.updateOne( cacheDocument.updateOne(Filters.eq("ip", toCache.getIp()), update,
Filters.eq("ip", toCache.getIp()), update, new UpdateOptions().upsert(true)); new UpdateOptions().upsert(true));
}); });
}
} }
}
@Override @Override
public void deleteResponse(String ip) { public void deleteResponse(String ip) {
cacheDocument.deleteMany(Filters.eq("ip", ip)); cacheDocument.deleteMany(Filters.eq("ip", ip));
}
@Override
public boolean isWhitelisted(UUID uuid) {
return settingsDocument
.find(
Filters.and(
Filters.eq("setting", "whitelist"), Filters.eq("uuid", uuid.toString())))
.first()
!= null;
}
@Override
public boolean isWhitelisted(String cidr) {
try {
return isWhitelisted(new CIDRUtils(cidr));
} catch (UnknownHostException e) {
AntiVPN.getInstance().getExecutor().log("Failed to check whitelist for IP: " + cidr, e);
return false;
} }
}
@Override @Override
public boolean isWhitelisted(CIDRUtils cidr) { public boolean isWhitelisted(UUID uuid) {
var start = new Decimal128(new BigDecimal(cidr.getStartIpInt())); return settingsDocument
var end = new Decimal128(new BigDecimal(cidr.getEndIpInt())); .find(Filters.and(Filters.eq("setting", "whitelist"),
return settingsDocument Filters.eq("uuid", uuid.toString()))).first() != null;
.find( }
Filters.and(
Filters.eq("setting", "whitelist"),
Filters.lte("ip_start", start),
Filters.gte("ip_end", end)))
.first()
!= null;
}
@Override @Override
public void addWhitelist(UUID uuid) { public boolean isWhitelisted(String cidr) {
Document wdoc = new Document("setting", "whitelist"); try {
wdoc.put("uuid", uuid.toString()); return isWhitelisted(new CIDRUtils(cidr));
AntiVPN.getInstance().getExecutor().getWhitelisted().add(uuid); } catch (UnknownHostException e) {
settingsDocument.insertOne(wdoc); AntiVPN.getInstance().getExecutor().log("Failed to check whitelist for IP: " + cidr, e);
} return false;
}
}
@Override @Override
public void removeWhitelist(UUID uuid) { public boolean isWhitelisted(CIDRUtils cidr) {
AntiVPN.getInstance().getExecutor().getWhitelisted().remove(uuid); var start = new Decimal128(new BigDecimal(cidr.getStartIpInt()));
settingsDocument.deleteMany( var end = new Decimal128(new BigDecimal(cidr.getEndIpInt()));
Filters.and(Filters.eq("setting", "whitelist"), Filters.eq("uuid", uuid.toString()))); return settingsDocument.find(Filters.and(Filters.eq("setting", "whitelist"),
} Filters.lte("ip_start", start), Filters.gte("ip_end", end))).first() != null;
}
@Override @Override
public void addWhitelist(CIDRUtils cidr) { public void addWhitelist(UUID uuid) {
Document doc = new Document("setting", "whitelist"); Document wdoc = new Document("setting", "whitelist");
doc.append("ip_start", new Decimal128(new BigDecimal(cidr.getStartIpInt()))); wdoc.put("uuid", uuid.toString());
doc.append("ip_end", new Decimal128(new BigDecimal(cidr.getEndIpInt()))); AntiVPN.getInstance().getExecutor().getWhitelisted().add(uuid);
doc.append("cidr_string", cidr.getCidr()); settingsDocument.insertOne(wdoc);
}
settingsDocument.insertOne(doc); @Override
} public void removeWhitelist(UUID uuid) {
AntiVPN.getInstance().getExecutor().getWhitelisted().remove(uuid);
settingsDocument.deleteMany(Filters
.and(
Filters.eq("setting", "whitelist"),
Filters.eq("uuid", uuid.toString())));
}
@Override @Override
public void removeWhitelist(CIDRUtils cidr) { public void addWhitelist(CIDRUtils cidr) {
settingsDocument.deleteMany( Document doc = new Document("setting", "whitelist");
Filters.and( doc.append("ip_start", new Decimal128(new BigDecimal(cidr.getStartIpInt())));
Filters.eq("setting", "whitelist"), doc.append("ip_end", new Decimal128(new BigDecimal(cidr.getEndIpInt())));
Filters.eq("ip_start", new Decimal128(new BigDecimal(cidr.getStartIpInt()))), doc.append("cidr_string", cidr.getCidr());
Filters.eq("ip_end", new Decimal128(new BigDecimal(cidr.getEndIpInt())))));
}
@Override settingsDocument.insertOne(doc);
public List<UUID> getAllWhitelisted() { }
List<UUID> uuids = new ArrayList<>();
settingsDocument
.find(Filters.and(Filters.eq("setting", "whitelist"), Filters.exists("uuid")))
.forEach(
(Consumer<? super Document>) doc -> uuids.add(UUID.fromString(doc.getString("uuid"))));
return uuids;
}
@Override @Override
public List<CIDRUtils> getAllWhitelistedIps() { public void removeWhitelist(CIDRUtils cidr) {
List<CIDRUtils> ips = new ArrayList<>(); settingsDocument.deleteMany(Filters
settingsDocument .and(
.find(Filters.and(Filters.eq("setting", "whitelist"), Filters.exists("cidr_string"))) Filters.eq("setting", "whitelist"),
.forEach( Filters.eq("ip_start", new Decimal128(new BigDecimal(cidr.getStartIpInt()))),
(Consumer<? super Document>) Filters.eq("ip_end", new Decimal128(new BigDecimal(cidr.getEndIpInt())))));
doc -> { }
try {
var cidr = new CIDRUtils(doc.getString("cidr_string")); @Override
ips.add(cidr); public List<UUID> getAllWhitelisted() {
} catch (UnknownHostException e) { List<UUID> uuids = new ArrayList<>();
AntiVPN.getInstance() settingsDocument.find(Filters.and(Filters.eq("setting", "whitelist"),
.getExecutor() Filters.exists("uuid")))
.logException( .forEach((Consumer<? super Document>) doc -> uuids.add(UUID.fromString(doc.getString("uuid"))));
"Could not format ip " + doc.getString("cidr_string") + " into a CIDR!", return uuids;
e); }
}
@Override
public List<CIDRUtils> getAllWhitelistedIps() {
List<CIDRUtils> ips = new ArrayList<>();
settingsDocument.find(Filters.and(Filters.eq("setting", "whitelist"),
Filters.exists("cidr_string"))).forEach((Consumer<? super Document>) doc -> {
try {
var cidr = new CIDRUtils(doc.getString("cidr_string"));
ips.add(cidr);
} catch (UnknownHostException e) {
AntiVPN.getInstance().getExecutor().logException("Could not format ip " + doc.getString("cidr_string") + " into a CIDR!", e);
}
}); });
return ips; return ips;
} }
@Override @Override
public void alertsState(UUID uuid, Consumer<Boolean> result) { public void alertsState(UUID uuid, Consumer<Boolean> result) {
AntiVPN.getInstance() AntiVPN.getInstance().getExecutor().getThreadExecutor().execute(() -> result.accept(settingsDocument
.getExecutor() .find(Filters.and(Filters.eq("setting", "alerts"),
.getThreadExecutor() Filters.eq("uuid", uuid.toString()))).first() != null));
.execute( }
() ->
result.accept(
settingsDocument
.find(
Filters.and(
Filters.eq("setting", "alerts"),
Filters.eq("uuid", uuid.toString())))
.first()
!= null));
}
@Override @Override
public void updateAlertsState(UUID uuid, boolean state) { public void updateAlertsState(UUID uuid, boolean state) {
AntiVPN.getInstance() AntiVPN.getInstance().getExecutor().getThreadExecutor().execute(() -> {
.getExecutor() settingsDocument.deleteMany(Filters.and(Filters.eq("setting", "alerts"),
.getThreadExecutor() Filters.eq("uuid", uuid.toString())));
.execute( if(state) {
() -> {
settingsDocument.deleteMany(
Filters.and(
Filters.eq("setting", "alerts"), Filters.eq("uuid", uuid.toString())));
if (state) {
Document adoc = new Document("setting", "alerts"); Document adoc = new Document("setting", "alerts");
adoc.put("uuid", uuid.toString()); adoc.put("uuid", uuid.toString());
settingsDocument.insertOne(adoc); settingsDocument.insertOne(adoc);
} }
}); });
}
@Override
public void clearResponses() {
cacheDocument.deleteMany(Filters.exists("ip"));
}
@Override
public void init() {
if (!AntiVPN.getInstance().getVpnConfig().mongoDatabaseURL().isEmpty()) { // URL
ConnectionString cs =
new ConnectionString(AntiVPN.getInstance().getVpnConfig().mongoDatabaseURL());
MongoClientSettings settings =
MongoClientSettings.builder().applyConnectionString(cs).build();
client = MongoClients.create(settings);
} else {
MongoClientSettings.Builder settingsBld =
MongoClientSettings.builder()
.readPreference(ReadPreference.nearest())
.applyToClusterSettings(
builder ->
builder.hosts(
Collections.singletonList(
new ServerAddress(
AntiVPN.getInstance().getVpnConfig().getIp(),
AntiVPN.getInstance().getVpnConfig().getPort()))));
if (AntiVPN.getInstance().getVpnConfig().useDatabaseCreds()) {
settingsBld.credential(
MongoCredential.createCredential(
AntiVPN.getInstance().getVpnConfig().getUsername(),
AntiVPN.getInstance().getVpnConfig().getDatabaseName(),
AntiVPN.getInstance().getVpnConfig().getPassword().toCharArray()));
}
client = MongoClients.create(settingsBld.build());
} }
antivpnDatabase = client.getDatabase(AntiVPN.getInstance().getVpnConfig().getDatabaseName());
settingsDocument = antivpnDatabase.getCollection("settings"); @Override
public void clearResponses() {
cacheDocument = antivpnDatabase.getCollection("cache"); cacheDocument.deleteMany(Filters.exists("ip"));
for (Version<MongoVPN> mongoDbVersion : Version.mongoDbVersions) {
if (mongoDbVersion.needsUpdate(this)) {
mongoDbVersion.update(this);
}
} }
}
@Override @Override
public void shutdown() { public void init() {
settingsDocument = null; if(!AntiVPN.getInstance().getVpnConfig().mongoDatabaseURL().isEmpty()) { //URL
cacheDocument = null; ConnectionString cs = new ConnectionString(AntiVPN.getInstance().getVpnConfig().mongoDatabaseURL());
client.close(); MongoClientSettings settings = MongoClientSettings.builder().applyConnectionString(cs).build();
} client = MongoClients.create(settings);
} else {
MongoClientSettings.Builder settingsBld = MongoClientSettings.builder().readPreference(ReadPreference.nearest())
.applyToClusterSettings(builder -> builder.
hosts(Collections.singletonList(
new ServerAddress(
AntiVPN.getInstance().getVpnConfig().getIp(),
AntiVPN.getInstance().getVpnConfig().getPort())
)));
if(AntiVPN.getInstance().getVpnConfig().useDatabaseCreds()) {
settingsBld.credential(MongoCredential
.createCredential(AntiVPN.getInstance().getVpnConfig().getUsername(),
AntiVPN.getInstance().getVpnConfig().getDatabaseName(),
AntiVPN.getInstance().getVpnConfig().getPassword().toCharArray()));
}
client = MongoClients.create(settingsBld.build());
}
antivpnDatabase = client.getDatabase(AntiVPN.getInstance().getVpnConfig().getDatabaseName());
settingsDocument = antivpnDatabase.getCollection("settings");
cacheDocument = antivpnDatabase.getCollection("cache");
for (Version<MongoVPN> mongoDbVersion : Version.mongoDbVersions) {
if(mongoDbVersion.needsUpdate(this)) {
mongoDbVersion.update(this);
}
}
}
@Override
public void shutdown() {
settingsDocument = null;
cacheDocument = null;
client.close();
}
} }
@@ -26,27 +26,27 @@ import org.bson.Document;
public class MongoFirst implements Version<MongoVPN> { public class MongoFirst implements Version<MongoVPN> {
@Override @Override
public void update(MongoVPN database) throws DatabaseException { public void update(MongoVPN database) throws DatabaseException {
if (database.settingsDocument.listIndexes().first() == null) { if(database.settingsDocument.listIndexes().first() == null) {
AntiVPN.getInstance().getExecutor().log("Created index for settings collection!"); AntiVPN.getInstance().getExecutor().log("Created index for settings collection!");
database.settingsDocument.createIndex(Indexes.ascending("ip")); database.settingsDocument.createIndex(Indexes.ascending("ip"));
database.settingsDocument.createIndex(Indexes.ascending("setting")); database.settingsDocument.createIndex(Indexes.ascending("setting"));
}
var versionCollect = database.antivpnDatabase.getCollection("version");
versionCollect.insertOne(new Document("version", versionNumber()));
} }
var versionCollect = database.antivpnDatabase.getCollection("version");
versionCollect.insertOne(new Document("version", versionNumber())); @Override
} public int versionNumber() {
return 0;
}
@Override @Override
public int versionNumber() { public boolean needsUpdate(MongoVPN database) {
return 0; var versionCollect = database.antivpnDatabase.getCollection("version");
}
@Override return versionCollect.find(Filters.eq("version", versionNumber())).first() == null;
public boolean needsUpdate(MongoVPN database) { }
var versionCollect = database.antivpnDatabase.getCollection("version");
return versionCollect.find(Filters.eq("version", versionNumber())).first() == null;
}
} }
@@ -23,68 +23,62 @@ import dev.brighten.antivpn.database.DatabaseException;
import dev.brighten.antivpn.database.mongo.MongoVPN; import dev.brighten.antivpn.database.mongo.MongoVPN;
import dev.brighten.antivpn.database.version.Version; import dev.brighten.antivpn.database.version.Version;
import dev.brighten.antivpn.utils.CIDRUtils; import dev.brighten.antivpn.utils.CIDRUtils;
import org.bson.Document;
import org.bson.types.Decimal128;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.function.Consumer; import java.util.function.Consumer;
import org.bson.Document;
import org.bson.types.Decimal128;
public class MongoSecond implements Version<MongoVPN> { public class MongoSecond implements Version<MongoVPN> {
@Override @Override
public void update(MongoVPN database) throws DatabaseException { public void update(MongoVPN database) throws DatabaseException {
List<Document> backup = new ArrayList<>(); List<Document> backup = new ArrayList<>();
database database.settingsDocument.find(Filters.and(Filters.eq("setting", "whitelist"),
.settingsDocument Filters.exists("ip")))
.find(Filters.and(Filters.eq("setting", "whitelist"), Filters.exists("ip"))) .forEach((Consumer<? super Document>) doc -> {
.forEach( backup.add(new Document(doc));
(Consumer<? super Document>)
doc -> {
backup.add(new Document(doc));
String ip = doc.getString("ip"); String ip = doc.getString("ip");
try { try {
var cidr = new CIDRUtils(ip + "/32"); var cidr = new CIDRUtils(ip + "/32");
doc.append("ip_start", new Decimal128(new BigDecimal(cidr.getStartIpInt()))); doc.append("ip_start", new Decimal128(new BigDecimal(cidr.getStartIpInt())));
doc.append("ip_end", new Decimal128(new BigDecimal(cidr.getEndIpInt()))); doc.append("ip_end", new Decimal128(new BigDecimal(cidr.getEndIpInt())));
doc.append("cidr_string", cidr.toString()); doc.append("cidr_string", cidr.toString());
doc.remove("ip"); doc.remove("ip");
database.settingsDocument.replaceOne( database.settingsDocument.replaceOne(Filters.eq("_id", doc.getObjectId("_id")), doc);
Filters.eq("_id", doc.getObjectId("_id")), doc); } catch (UnknownHostException e) {
} catch (UnknownHostException e) { rollback(backup, database);
rollback(backup, database); throw new RuntimeException(e);
throw new RuntimeException(e); }
}
}); });
database.settingsDocument.createIndex( database.settingsDocument.createIndex(Indexes.compoundIndex(Indexes.ascending("ip_start"), Indexes.ascending("ip_end")));
Indexes.compoundIndex(Indexes.ascending("ip_start"), Indexes.ascending("ip_end"))); database.settingsDocument.createIndex(Indexes.ascending("cidr_string"));
database.settingsDocument.createIndex(Indexes.ascending("cidr_string")); var versionCollect = database.antivpnDatabase.getCollection("version");
var versionCollect = database.antivpnDatabase.getCollection("version"); versionCollect.insertOne(new Document("version", versionNumber()));
versionCollect.insertOne(new Document("version", versionNumber())); }
}
private void rollback(List<Document> toRollback, MongoVPN database) { private void rollback(List<Document> toRollback, MongoVPN database) {
AntiVPN.getInstance().getExecutor().log("Rolling back to version 0..."); AntiVPN.getInstance().getExecutor().log("Rolling back to version 0...");
toRollback.forEach( toRollback.forEach(doc -> database.settingsDocument.replaceOne(Filters.eq("_id", doc.getObjectId("_id")), doc));
doc -> toRollback.clear();
database.settingsDocument.replaceOne(Filters.eq("_id", doc.getObjectId("_id")), doc)); }
toRollback.clear();
}
@Override @Override
public int versionNumber() { public int versionNumber() {
return 1; return 1;
} }
@Override @Override
public boolean needsUpdate(MongoVPN database) { public boolean needsUpdate(MongoVPN database) {
var versionCollect = database.antivpnDatabase.getCollection("version"); var versionCollect = database.antivpnDatabase.getCollection("version");
return versionCollect.find(Filters.eq("version", versionNumber())).first() == null; return versionCollect.find(Filters.eq("version", versionNumber())).first() == null;
} }
} }
@@ -23,6 +23,9 @@ import dev.brighten.antivpn.database.mongo.MongoVPN;
import dev.brighten.antivpn.database.version.Version; import dev.brighten.antivpn.database.version.Version;
import dev.brighten.antivpn.utils.CIDRUtils; import dev.brighten.antivpn.utils.CIDRUtils;
import dev.brighten.antivpn.utils.MiscUtils; import dev.brighten.antivpn.utils.MiscUtils;
import org.bson.Document;
import org.bson.types.Decimal128;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.math.BigInteger; import java.math.BigInteger;
import java.net.UnknownHostException; import java.net.UnknownHostException;
@@ -30,108 +33,76 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.logging.Level; import java.util.logging.Level;
import org.bson.Document;
import org.bson.types.Decimal128;
public class MongoThird implements Version<MongoVPN> { public class MongoThird implements Version<MongoVPN> {
@Override @Override
public void update(MongoVPN database) throws DatabaseException { public void update(MongoVPN database) throws DatabaseException {
List<CIDRUtils> ipRanges = new ArrayList<>(); List<CIDRUtils> ipRanges = new ArrayList<>();
List<CIDRUtils> rangesToInsert = new ArrayList<>(); List<CIDRUtils> rangesToInsert = new ArrayList<>();
List<BigInteger[]> rangesToRemove = new ArrayList<>(); List<BigInteger[]> rangesToRemove = new ArrayList<>();
database database.settingsDocument.find(Filters.and(Filters.eq("setting", "whitelist"), Filters.exists("cidr_string")))
.settingsDocument .forEach((Consumer<? super Document>) doc -> {
.find(Filters.and(Filters.eq("setting", "whitelist"), Filters.exists("cidr_string"))) BigInteger start = doc.get("ip_start", Decimal128.class).bigDecimalValue().toBigInteger();
.forEach( BigInteger end = doc.get("ip_end", Decimal128.class).bigDecimalValue().toBigInteger();
(Consumer<? super Document>)
doc -> {
BigInteger start =
doc.get("ip_start", Decimal128.class).bigDecimalValue().toBigInteger();
BigInteger end =
doc.get("ip_end", Decimal128.class).bigDecimalValue().toBigInteger();
try { try {
var range = MiscUtils.rangeToCidrs(start, end); var range = MiscUtils.rangeToCidrs(start, end);
if (range.size() > 1) { if(range.size() > 1) {
rangesToRemove.add(new BigInteger[] {start, end}); rangesToRemove.add(new BigInteger[]{start, end});
rangesToInsert.addAll(range); rangesToInsert.addAll(range);
AntiVPN.getInstance() AntiVPN.getInstance().getExecutor().log(Level.WARNING, "Found multiple CIDR ranges for whitelist range for %s, %s!", start, end);
.getExecutor() } else ipRanges.addAll(range);
.log( } catch (UnknownHostException e) {
Level.WARNING, AntiVPN.getInstance().getExecutor().logException(
"Found multiple CIDR ranges for whitelist range for %s, %s!", String.format("Could not convert ip range to CIDR! %s, %s", start, end), e);
start, }
end);
} else ipRanges.addAll(range);
} catch (UnknownHostException e) {
AntiVPN.getInstance()
.getExecutor()
.logException(
String.format("Could not convert ip range to CIDR! %s, %s", start, end),
e);
}
}); });
if (!rangesToInsert.isEmpty()) { if(!rangesToInsert.isEmpty()) {
AntiVPN.getInstance() AntiVPN.getInstance().getExecutor().log("Inserting %s new ranges into database...", rangesToInsert.size());
.getExecutor() var documentsToInsert = rangesToInsert.stream().map(cidr -> {
.log("Inserting %s new ranges into database...", rangesToInsert.size()); Document doc = new Document("setting", "whitelist");
var documentsToInsert = doc.append("ip_start", new Decimal128(new BigDecimal(cidr.getStartIpInt())));
rangesToInsert.stream() doc.append("ip_end", new Decimal128(new BigDecimal(cidr.getEndIpInt())));
.map( doc.append("cidr_string", cidr.getCidr());
cidr -> {
Document doc = new Document("setting", "whitelist");
doc.append("ip_start", new Decimal128(new BigDecimal(cidr.getStartIpInt())));
doc.append("ip_end", new Decimal128(new BigDecimal(cidr.getEndIpInt())));
doc.append("cidr_string", cidr.getCidr());
return doc; return doc;
}) }).toList();
.toList();
database.settingsDocument.insertMany(documentsToInsert); database.settingsDocument.insertMany(documentsToInsert);
} }
if (!rangesToRemove.isEmpty()) { if(!rangesToRemove.isEmpty()) {
AntiVPN.getInstance() AntiVPN.getInstance().getExecutor().log("Removing %s old ranges from database...", rangesToRemove.size());
.getExecutor() rangesToRemove.forEach(range -> database.settingsDocument
.log("Removing %s old ranges from database...", rangesToRemove.size()); .deleteMany(Filters.and(
rangesToRemove.forEach( Filters.gte("ip_start", new Decimal128(new BigDecimal(range[0]))),
range -> Filters.lte("ip_end", new Decimal128(new BigDecimal(range[1]))))));
database.settingsDocument.deleteMany( }
Filters.and(
Filters.gte("ip_start", new Decimal128(new BigDecimal(range[0]))), if(!ipRanges.isEmpty()) {
Filters.lte("ip_end", new Decimal128(new BigDecimal(range[1])))))); AntiVPN.getInstance().getExecutor().log("Updating %s CIDRs in database with proper notation...", ipRanges.size());
ipRanges.forEach(cidr -> database.settingsDocument
.updateMany(Filters.and(Filters.eq("setting", "whitelist"),
Filters.eq("ip_start", new Decimal128(new BigDecimal(cidr.getStartIpInt()))),
Filters.eq("ip_end", new Decimal128(new BigDecimal(cidr.getEndIpInt())))),
new Document("$set", new Document("cidr_string", cidr.getCidr()))));
}
var versionCollect = database.antivpnDatabase.getCollection("version");
versionCollect.insertOne(new Document("version", versionNumber()));
} }
if (!ipRanges.isEmpty()) { @Override
AntiVPN.getInstance() public int versionNumber() {
.getExecutor() return 2;
.log("Updating %s CIDRs in database with proper notation...", ipRanges.size());
ipRanges.forEach(
cidr ->
database.settingsDocument.updateMany(
Filters.and(
Filters.eq("setting", "whitelist"),
Filters.eq("ip_start", new Decimal128(new BigDecimal(cidr.getStartIpInt()))),
Filters.eq("ip_end", new Decimal128(new BigDecimal(cidr.getEndIpInt())))),
new Document("$set", new Document("cidr_string", cidr.getCidr()))));
} }
var versionCollect = database.antivpnDatabase.getCollection("version"); @Override
versionCollect.insertOne(new Document("version", versionNumber())); public boolean needsUpdate(MongoVPN database) {
} var versionCollect = database.antivpnDatabase.getCollection("version");
@Override return versionCollect.find(Filters.eq("version", versionNumber())).first() == null;
public int versionNumber() { }
return 2;
}
@Override
public boolean needsUpdate(MongoVPN database) {
var versionCollect = database.antivpnDatabase.getCollection("version");
return versionCollect.find(Filters.eq("version", versionNumber())).first() == null;
}
} }
@@ -20,55 +20,45 @@ import dev.brighten.antivpn.AntiVPN;
import dev.brighten.antivpn.database.local.H2VPN; import dev.brighten.antivpn.database.local.H2VPN;
import dev.brighten.antivpn.database.sql.utils.MySQL; import dev.brighten.antivpn.database.sql.utils.MySQL;
import dev.brighten.antivpn.database.version.Version; import dev.brighten.antivpn.database.version.Version;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
public class MySqlVPN extends H2VPN { public class MySqlVPN extends H2VPN {
public MySqlVPN() { public MySqlVPN() {
AntiVPN.getInstance() AntiVPN.getInstance().getExecutor().getThreadExecutor().scheduleAtFixedRate(() -> {
.getExecutor() if(!AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled() || MySQL.isClosed()) return;
.getThreadExecutor()
.scheduleAtFixedRate(
() -> {
if (!AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled() || MySQL.isClosed())
return;
// Refreshing whitelisted players //Refreshing whitelisted players
AntiVPN.getInstance().getExecutor().getWhitelisted().clear(); AntiVPN.getInstance().getExecutor().getWhitelisted().clear();
AntiVPN.getInstance() AntiVPN.getInstance().getExecutor().getWhitelisted()
.getExecutor() .addAll(AntiVPN.getInstance().getDatabase().getAllWhitelisted());
.getWhitelisted()
.addAll(AntiVPN.getInstance().getDatabase().getAllWhitelisted());
// Refreshing whitlisted IPs //Refreshing whitlisted IPs
AntiVPN.getInstance().getExecutor().getWhitelistedIps().clear(); AntiVPN.getInstance().getExecutor().getWhitelistedIps().clear();
AntiVPN.getInstance() AntiVPN.getInstance().getExecutor().getWhitelistedIps()
.getExecutor() .addAll(AntiVPN.getInstance().getDatabase().getAllWhitelistedIps());
.getWhitelistedIps() }, 2, 30, TimeUnit.SECONDS);
.addAll(AntiVPN.getInstance().getDatabase().getAllWhitelistedIps()); }
},
2, @Override
30, public void init() {
TimeUnit.SECONDS); if (!AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled())
} return;
AntiVPN.getInstance().getExecutor().log("Initializing MySQL...");
@Override MySQL.init();
public void init() {
if (!AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled()) return; AntiVPN.getInstance().getExecutor().log("Checking for updates...");
AntiVPN.getInstance().getExecutor().log("Initializing MySQL...");
MySQL.init(); //Running check for old table types to update
try {
AntiVPN.getInstance().getExecutor().log("Checking for updates..."); for (Version<MySqlVPN> version : Version.mysqlVersions) {
if(version.needsUpdate(this)) {
// Running check for old table types to update version.update(this);
try { }
for (Version<MySqlVPN> version : Version.mysqlVersions) { }
if (version.needsUpdate(this)) { } catch (Exception e) {
version.update(this); throw new RuntimeException("Could not complete version setup due to SQL error", e);
} }
}
} catch (Exception e) {
throw new RuntimeException("Could not complete version setup due to SQL error", e);
} }
}
} }
@@ -16,130 +16,132 @@
package dev.brighten.antivpn.database.sql.utils; package dev.brighten.antivpn.database.sql.utils;
import java.sql.*;
import java.util.UUID;
import lombok.Getter; import lombok.Getter;
import lombok.SneakyThrows; import lombok.SneakyThrows;
import java.sql.*;
import java.util.UUID;
public class ExecutableStatement implements AutoCloseable { public class ExecutableStatement implements AutoCloseable {
@Getter private final PreparedStatement statement; @Getter
private int pos = 1; private final PreparedStatement statement;
private int pos = 1;
public ExecutableStatement(PreparedStatement statement) { public ExecutableStatement(PreparedStatement statement) {
this.statement = statement; this.statement = statement;
}
public int execute() throws SQLException {
return statement.executeUpdate();
}
public void execute(ResultSetIterator iterator) throws SQLException {
try (var rs = statement.executeQuery()) {
while (rs.next()) iterator.next(rs);
} }
}
public int[] executeBatch() throws SQLException { public int execute() throws SQLException {
return statement.executeBatch(); return statement.executeUpdate();
} }
public ResultSet executeQuery() throws SQLException { public void execute(ResultSetIterator iterator) throws SQLException {
return statement.executeQuery(); try(var rs = statement.executeQuery()) {
} while (rs.next()) iterator.next(rs);
}
}
@SneakyThrows public int[] executeBatch() throws SQLException {
public ExecutableStatement append(Object obj) { return statement.executeBatch();
statement.setObject(pos++, obj); }
return this;
}
@SneakyThrows public ResultSet executeQuery() throws SQLException {
public ExecutableStatement append(String obj) { return statement.executeQuery();
statement.setString(pos++, obj); }
return this;
}
@SneakyThrows @SneakyThrows
public ExecutableStatement append(UUID uuid) { public ExecutableStatement append(Object obj) {
if (uuid != null) statement.setString(pos++, uuid.toString().replace("-", "")); statement.setObject(pos++, obj);
else statement.setString(pos++, null); return this;
return this; }
}
@SneakyThrows @SneakyThrows
public ExecutableStatement append(Array obj) { public ExecutableStatement append(String obj) {
statement.setArray(pos++, obj); statement.setString(pos++, obj);
return this; return this;
} }
@SneakyThrows @SneakyThrows
public ExecutableStatement append(Integer obj) { public ExecutableStatement append(UUID uuid) {
statement.setInt(pos++, obj); if (uuid != null) statement.setString(pos++, uuid.toString().replace("-", ""));
return this; else statement.setString(pos++, null);
} return this;
}
@SneakyThrows @SneakyThrows
public ExecutableStatement append(Short obj) { public ExecutableStatement append(Array obj) {
statement.setShort(pos++, obj); statement.setArray(pos++, obj);
return this; return this;
} }
@SneakyThrows @SneakyThrows
public ExecutableStatement append(Long obj) { public ExecutableStatement append(Integer obj) {
statement.setLong(pos++, obj); statement.setInt(pos++, obj);
return this; return this;
} }
@SneakyThrows @SneakyThrows
public ExecutableStatement append(Float obj) { public ExecutableStatement append(Short obj) {
statement.setFloat(pos++, obj); statement.setShort(pos++, obj);
return this; return this;
} }
@SneakyThrows @SneakyThrows
public ExecutableStatement append(Double obj) { public ExecutableStatement append(Long obj) {
statement.setDouble(pos++, obj); statement.setLong(pos++, obj);
return this; return this;
} }
@SneakyThrows @SneakyThrows
public ExecutableStatement append(Date obj) { public ExecutableStatement append(Float obj) {
statement.setDate(pos++, obj); statement.setFloat(pos++, obj);
return this; return this;
} }
@SneakyThrows @SneakyThrows
public ExecutableStatement append(Timestamp obj) { public ExecutableStatement append(Double obj) {
statement.setTimestamp(pos++, obj); statement.setDouble(pos++, obj);
return this; return this;
} }
@SneakyThrows @SneakyThrows
public ExecutableStatement append(Time obj) { public ExecutableStatement append(Date obj) {
statement.setTime(pos++, obj); statement.setDate(pos++, obj);
return this; return this;
} }
@SneakyThrows @SneakyThrows
public ExecutableStatement append(Blob obj) { public ExecutableStatement append(Timestamp obj) {
statement.setBlob(pos++, obj); statement.setTimestamp(pos++, obj);
return this; return this;
} }
@SneakyThrows @SneakyThrows
public ExecutableStatement append(byte[] obj) { public ExecutableStatement append(Time obj) {
statement.setBytes(pos++, obj); statement.setTime(pos++, obj);
return this; return this;
} }
@SneakyThrows @SneakyThrows
public ExecutableStatement addBatch() { public ExecutableStatement append(Blob obj) {
statement.addBatch(); statement.setBlob(pos++, obj);
return this; return this;
} }
@Override @SneakyThrows
public void close() throws SQLException { public ExecutableStatement append(byte[] obj) {
statement.close(); statement.setBytes(pos++, obj);
} return this;
}
@SneakyThrows
public ExecutableStatement addBatch() {
statement.addBatch();
return this;
}
@Override
public void close() throws SQLException {
statement.close();
}
} }
@@ -18,6 +18,9 @@ package dev.brighten.antivpn.database.sql.utils;
import com.mysql.cj.jdbc.Driver; import com.mysql.cj.jdbc.Driver;
import dev.brighten.antivpn.AntiVPN; import dev.brighten.antivpn.AntiVPN;
import org.h2.jdbc.JdbcSQLFeatureNotSupportedException;
import org.h2.jdbc.JdbcSQLNonTransientConnectionException;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Files; import java.nio.file.Files;
@@ -26,194 +29,167 @@ import java.sql.SQLException;
import java.sql.SQLSyntaxErrorException; import java.sql.SQLSyntaxErrorException;
import java.util.Properties; import java.util.Properties;
import java.util.logging.Level; import java.util.logging.Level;
import org.h2.jdbc.JdbcSQLFeatureNotSupportedException;
import org.h2.jdbc.JdbcSQLNonTransientConnectionException;
public class MySQL { public class MySQL {
private static Connection conn; private static Connection conn;
public static void init() { public static void init() {
try { try {
if (conn == null || conn.isClosed()) { if (conn == null || conn.isClosed()) {
String url = String url = "jdbc:mysql://" + AntiVPN.getInstance().getVpnConfig().getIp()
"jdbc:mysql://" + ":" + AntiVPN.getInstance().getVpnConfig().getPort()
+ AntiVPN.getInstance().getVpnConfig().getIp() + "/?useSSL=true&autoReconnect=true";
+ ":" Properties properties = new Properties();
+ AntiVPN.getInstance().getVpnConfig().getPort() properties.setProperty("user", AntiVPN.getInstance().getVpnConfig().getUsername());
+ "/?useSSL=true&autoReconnect=true"; properties.setProperty("password", AntiVPN.getInstance().getVpnConfig().getPassword());
Properties properties = new Properties();
properties.setProperty("user", AntiVPN.getInstance().getVpnConfig().getUsername());
properties.setProperty("password", AntiVPN.getInstance().getVpnConfig().getPassword());
conn = new Driver().connect(url, properties); conn = new Driver().connect(url, properties);
if (conn == null) { if (conn == null) {
throw new SQLException("MySQL driver did not accept URL: " + url); throw new SQLException("MySQL driver did not accept URL: " + url);
}
conn.setAutoCommit(true);
Query.use(conn);
String databaseName = AntiVPN.getInstance().getVpnConfig().getDatabaseName();
try {
Query.prepare("CREATE DATABASE IF NOT EXISTS `" + databaseName + "`").execute();
} catch (SQLException ex) {
if (!isDatabaseCreationPermissionIssue(ex)) {
throw ex;
}
AntiVPN.getInstance().getExecutor().log(
"No permission to create MySQL database `" + databaseName
+ "`. Attempting to use the existing database instead.");
}
Query.prepare("USE `" + databaseName + "`").execute();
AntiVPN.getInstance().getExecutor().log("Connection to MySQL has been established.");
}
} catch (Exception e) {
AntiVPN.getInstance().getExecutor().logException("Failed to load mysql: " + e.getMessage(), e);
throw new RuntimeException("Could not initialize MySQL connection", e);
}
}
private static boolean isDatabaseCreationPermissionIssue(SQLException ex) {
return ex instanceof SQLSyntaxErrorException
&& ex.getMessage() != null
&& ex.getMessage().contains("Access denied");
}
public static void initH2() {
initH2(true);
}
private static void initH2(boolean allowRetry) {
File dataFolder = new File(AntiVPN.getInstance().getPluginFolder(), "databases");
if (!dataFolder.exists() && dataFolder.mkdirs()) {
AntiVPN.getInstance().getExecutor().log("Created database directory");
}
File dbFile = new File(dataFolder, "database.mv.db");
File databaseFile = new File(dataFolder, "database");
try {
conn = new NonClosableConnection(new org.h2.jdbc.JdbcConnection("jdbc:h2:file:" +
databaseFile.getAbsolutePath(),
new Properties(), AntiVPN.getInstance().getVpnConfig().getUsername(),
AntiVPN.getInstance().getVpnConfig().getPassword(), false));
conn.setAutoCommit(true);
Query.use(conn);
AntiVPN.getInstance().getExecutor().log("Connection to H2 has been established.");
} catch (SQLException ex) {
AntiVPN.getInstance().getExecutor().logException("H2 exception on initialize", ex);
if(ex instanceof JdbcSQLFeatureNotSupportedException
|| ex instanceof JdbcSQLNonTransientConnectionException) {
AntiVPN.getInstance().getExecutor()
.log("H2 database file is incompatible with this version of AntiVPN. " +
"Backing up old database file...");
shutdown();
if (allowRetry && backupOldDB(dbFile, dataFolder)) {
initH2(false);
} else {
AntiVPN.getInstance().getExecutor().log(
"Could not back up and remove the incompatible H2 database file automatically.");
}
} else {
AntiVPN.getInstance().getExecutor().logException("Failed to load H2 database: " + ex.getCause().toString(), ex);
}
} catch (Exception e) {
AntiVPN.getInstance().getExecutor().logException("Failed to load H2 database: " + e.getMessage(), e);
AntiVPN.getInstance().getExecutor().log(Level.INFO, "TIP: Try deleting the plugin folder and restarting your server!");
}
}
public static boolean backupOldDB(File dbFile, File dataFolder) {
if (!dbFile.exists()) {
return true;
}
if (!dbFile.isFile()) {
AntiVPN.getInstance().getExecutor().log("Skipping backup for non-file path: " + dbFile.getAbsolutePath());
return false;
} }
conn.setAutoCommit(true);
Query.use(conn);
String databaseName = AntiVPN.getInstance().getVpnConfig().getDatabaseName();
try { try {
Query.prepare("CREATE DATABASE IF NOT EXISTS `" + databaseName + "`").execute(); File backupDir = new File(dataFolder, "backups");
} catch (SQLException ex) { if(backupDir.mkdirs()) {
if (!isDatabaseCreationPermissionIssue(ex)) { AntiVPN.getInstance().getExecutor().log("Created backup directory");
throw ex; } else if (backupDir.exists()) {
} AntiVPN.getInstance().getExecutor().log("Backup directory already exists");
} else {
AntiVPN.getInstance().getExecutor().log("Could not create backup directory");
return false;
}
File backupFile = new File(backupDir, dbFile.getName() + ".backup_" + System.currentTimeMillis());
Files.copy(dbFile.toPath(), backupFile.toPath());
AntiVPN.getInstance() if (!dbFile.delete()) {
.getExecutor() dbFile.deleteOnExit();
.log( AntiVPN.getInstance().getExecutor().log("Could not delete database file - will try again on shutdown");
"No permission to create MySQL database `" return false;
+ databaseName }
+ "`. Attempting to use the existing database instead.");
AntiVPN.getInstance().getExecutor().log("Successfully deleted incompatible database file");
return true;
} catch (IOException ex) {
AntiVPN.getInstance().getExecutor().logException("Failed to handle database file", ex);
} }
Query.prepare("USE `" + databaseName + "`").execute(); return false;
AntiVPN.getInstance().getExecutor().log("Connection to MySQL has been established.");
}
} catch (Exception e) {
AntiVPN.getInstance()
.getExecutor()
.logException("Failed to load mysql: " + e.getMessage(), e);
throw new RuntimeException("Could not initialize MySQL connection", e);
}
}
private static boolean isDatabaseCreationPermissionIssue(SQLException ex) {
return ex instanceof SQLSyntaxErrorException
&& ex.getMessage() != null
&& ex.getMessage().contains("Access denied");
}
public static void initH2() {
initH2(true);
}
private static void initH2(boolean allowRetry) {
File dataFolder = new File(AntiVPN.getInstance().getPluginFolder(), "databases");
if (!dataFolder.exists() && dataFolder.mkdirs()) {
AntiVPN.getInstance().getExecutor().log("Created database directory");
} }
File dbFile = new File(dataFolder, "database.mv.db"); public static void use() {
try {
File databaseFile = new File(dataFolder, "database"); init();
try { } catch (Exception e) {
conn = AntiVPN.getInstance().getExecutor().logException(e);
new NonClosableConnection(
new org.h2.jdbc.JdbcConnection(
"jdbc:h2:file:" + databaseFile.getAbsolutePath(),
new Properties(),
AntiVPN.getInstance().getVpnConfig().getUsername(),
AntiVPN.getInstance().getVpnConfig().getPassword(),
false));
conn.setAutoCommit(true);
Query.use(conn);
AntiVPN.getInstance().getExecutor().log("Connection to H2 has been established.");
} catch (SQLException ex) {
AntiVPN.getInstance().getExecutor().logException("H2 exception on initialize", ex);
if (ex instanceof JdbcSQLFeatureNotSupportedException
|| ex instanceof JdbcSQLNonTransientConnectionException) {
AntiVPN.getInstance()
.getExecutor()
.log(
"H2 database file is incompatible with this version of AntiVPN. "
+ "Backing up old database file...");
shutdown();
if (allowRetry && backupOldDB(dbFile, dataFolder)) {
initH2(false);
} else {
AntiVPN.getInstance()
.getExecutor()
.log("Could not back up and remove the incompatible H2 database file automatically.");
} }
} else {
AntiVPN.getInstance()
.getExecutor()
.logException("Failed to load H2 database: " + ex.getCause().toString(), ex);
}
} catch (Exception e) {
AntiVPN.getInstance()
.getExecutor()
.logException("Failed to load H2 database: " + e.getMessage(), e);
AntiVPN.getInstance()
.getExecutor()
.log(Level.INFO, "TIP: Try deleting the plugin folder and restarting your server!");
}
}
public static boolean backupOldDB(File dbFile, File dataFolder) {
if (!dbFile.exists()) {
return true;
} }
if (!dbFile.isFile()) { public static void shutdown() {
AntiVPN.getInstance() try {
.getExecutor() if(conn != null && !conn.isClosed()) {
.log("Skipping backup for non-file path: " + dbFile.getAbsolutePath()); if(conn instanceof NonClosableConnection) {
return false; ((NonClosableConnection)conn).shutdown();
} else conn.close();
conn = null;
}
} catch (Exception e) {
AntiVPN.getInstance().getExecutor().logException(e);
}
} }
try { public static boolean isClosed() {
File backupDir = new File(dataFolder, "backups"); if(conn == null)
if (backupDir.mkdirs()) { return true;
AntiVPN.getInstance().getExecutor().log("Created backup directory");
} else if (backupDir.exists()) {
AntiVPN.getInstance().getExecutor().log("Backup directory already exists");
} else {
AntiVPN.getInstance().getExecutor().log("Could not create backup directory");
return false;
}
File backupFile =
new File(backupDir, dbFile.getName() + ".backup_" + System.currentTimeMillis());
Files.copy(dbFile.toPath(), backupFile.toPath());
if (!dbFile.delete()) { try {
dbFile.deleteOnExit(); return conn.isClosed();
AntiVPN.getInstance() } catch (SQLException e) {
.getExecutor() AntiVPN.getInstance().getExecutor().logException(e);
.log("Could not delete database file - will try again on shutdown"); return true;
return false; }
}
AntiVPN.getInstance().getExecutor().log("Successfully deleted incompatible database file");
return true;
} catch (IOException ex) {
AntiVPN.getInstance().getExecutor().logException("Failed to handle database file", ex);
} }
return false;
}
public static void use() {
try {
init();
} catch (Exception e) {
AntiVPN.getInstance().getExecutor().logException(e);
}
}
public static void shutdown() {
try {
if (conn != null && !conn.isClosed()) {
if (conn instanceof NonClosableConnection) {
((NonClosableConnection) conn).shutdown();
} else conn.close();
conn = null;
}
} catch (Exception e) {
AntiVPN.getInstance().getExecutor().logException(e);
}
}
public static boolean isClosed() {
if (conn == null) return true;
try {
return conn.isClosed();
} catch (SQLException e) {
AntiVPN.getInstance().getExecutor().logException(e);
return true;
}
}
} }
@@ -25,299 +25,89 @@ import java.util.concurrent.Executor;
* A wrapper around a {@link Connection} which blocks usage of the default {@link #close()} method. * A wrapper around a {@link Connection} which blocks usage of the default {@link #close()} method.
*/ */
public class NonClosableConnection implements Connection { public class NonClosableConnection implements Connection {
private final Connection delegate; private final Connection delegate;
public NonClosableConnection(Connection delegate) { public NonClosableConnection(Connection delegate) {
this.delegate = delegate; this.delegate = delegate;
}
/** Actually {@link #close() closes} the underlying connection. */
public final void shutdown() throws SQLException {
this.delegate.close();
}
@Override
public final void close() throws SQLException {
// do nothing
}
@Override
public final boolean isWrapperFor(Class<?> iface) throws SQLException {
return iface.isInstance(this.delegate) || this.delegate.isWrapperFor(iface);
}
@SuppressWarnings("unchecked")
@Override
public final <T> T unwrap(Class<T> iface) throws SQLException {
if (iface.isInstance(this.delegate)) {
return (T) this.delegate;
} }
return this.delegate.unwrap(iface);
}
// Forward to the delegate connection /**
@Override * Actually {@link #close() closes} the underlying connection.
public Statement createStatement() throws SQLException { */
return this.delegate.createStatement(); public final void shutdown() throws SQLException {
} this.delegate.close();
}
@Override @Override
public PreparedStatement prepareStatement(String sql) throws SQLException { public final void close() throws SQLException {
return this.delegate.prepareStatement(sql); // do nothing
} }
@Override @Override
public CallableStatement prepareCall(String sql) throws SQLException { public final boolean isWrapperFor(Class<?> iface) throws SQLException {
return this.delegate.prepareCall(sql); return iface.isInstance(this.delegate) || this.delegate.isWrapperFor(iface);
} }
@Override @SuppressWarnings("unchecked")
public String nativeSQL(String sql) throws SQLException { @Override
return this.delegate.nativeSQL(sql); public final <T> T unwrap(Class<T> iface) throws SQLException {
} if (iface.isInstance(this.delegate)) {
return (T) this.delegate;
}
return this.delegate.unwrap(iface);
}
@Override // Forward to the delegate connection
public void setAutoCommit(boolean autoCommit) throws SQLException { @Override public Statement createStatement() throws SQLException { return this.delegate.createStatement(); }
this.delegate.setAutoCommit(autoCommit); @Override public PreparedStatement prepareStatement(String sql) throws SQLException { return this.delegate.prepareStatement(sql); }
} @Override public CallableStatement prepareCall(String sql) throws SQLException { return this.delegate.prepareCall(sql); }
@Override public String nativeSQL(String sql) throws SQLException { return this.delegate.nativeSQL(sql); }
@Override public void setAutoCommit(boolean autoCommit) throws SQLException { this.delegate.setAutoCommit(autoCommit); }
@Override public boolean getAutoCommit() throws SQLException { return this.delegate.getAutoCommit(); }
@Override public void commit() throws SQLException { this.delegate.commit(); }
@Override public void rollback() throws SQLException { this.delegate.rollback(); }
@Override public boolean isClosed() throws SQLException { return this.delegate.isClosed(); }
@Override public DatabaseMetaData getMetaData() throws SQLException { return this.delegate.getMetaData(); }
@Override public void setReadOnly(boolean readOnly) throws SQLException { this.delegate.setReadOnly(readOnly); }
@Override public boolean isReadOnly() throws SQLException { return this.delegate.isReadOnly(); }
@Override public void setCatalog(String catalog) throws SQLException { this.delegate.setCatalog(catalog); }
@Override public String getCatalog() throws SQLException { return this.delegate.getCatalog(); }
@Override public void setTransactionIsolation(int level) throws SQLException { this.delegate.setTransactionIsolation(level); }
@Override public int getTransactionIsolation() throws SQLException { return this.delegate.getTransactionIsolation(); }
@Override public SQLWarning getWarnings() throws SQLException { return this.delegate.getWarnings(); }
@Override public void clearWarnings() throws SQLException { this.delegate.clearWarnings(); }
@Override public Statement createStatement(int resultSetType, int resultSetConcurrency) throws SQLException { return this.delegate.createStatement(resultSetType, resultSetConcurrency); }
@Override public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency) throws SQLException { return this.delegate.prepareStatement(sql, resultSetType, resultSetConcurrency); }
@Override public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency) throws SQLException { return this.delegate.prepareCall(sql, resultSetType, resultSetConcurrency); }
@Override public Map<String, Class<?>> getTypeMap() throws SQLException { return this.delegate.getTypeMap(); }
@Override public void setTypeMap(Map<String, Class<?>> map) throws SQLException { this.delegate.setTypeMap(map); }
@Override public void setHoldability(int holdability) throws SQLException { this.delegate.setHoldability(holdability); }
@Override public int getHoldability() throws SQLException { return this.delegate.getHoldability(); }
@Override public Savepoint setSavepoint() throws SQLException { return this.delegate.setSavepoint(); }
@Override public Savepoint setSavepoint(String name) throws SQLException { return this.delegate.setSavepoint(name); }
@Override public void rollback(Savepoint savepoint) throws SQLException { this.delegate.rollback(savepoint); }
@Override public void releaseSavepoint(Savepoint savepoint) throws SQLException { this.delegate.releaseSavepoint(savepoint); }
@Override public Statement createStatement(int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException { return this.delegate.createStatement(resultSetType, resultSetConcurrency, resultSetHoldability); }
@Override public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException { return this.delegate.prepareStatement(sql, resultSetType, resultSetConcurrency, resultSetHoldability); }
@Override public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException { return this.delegate.prepareCall(sql, resultSetType, resultSetConcurrency, resultSetHoldability); }
@Override public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) throws SQLException { return this.delegate.prepareStatement(sql, autoGeneratedKeys); }
@Override public PreparedStatement prepareStatement(String sql, int[] columnIndexes) throws SQLException { return this.delegate.prepareStatement(sql, columnIndexes); }
@Override public PreparedStatement prepareStatement(String sql, String[] columnNames) throws SQLException { return this.delegate.prepareStatement(sql, columnNames); }
@Override public Clob createClob() throws SQLException { return this.delegate.createClob(); }
@Override public Blob createBlob() throws SQLException { return this.delegate.createBlob(); }
@Override public NClob createNClob() throws SQLException { return this.delegate.createNClob(); }
@Override public SQLXML createSQLXML() throws SQLException { return this.delegate.createSQLXML(); }
@Override public boolean isValid(int timeout) throws SQLException { return this.delegate.isValid(timeout); }
@Override public void setClientInfo(String name, String value) throws SQLClientInfoException { this.delegate.setClientInfo(name, value); }
@Override public void setClientInfo(Properties properties) throws SQLClientInfoException { this.delegate.setClientInfo(properties); }
@Override public String getClientInfo(String name) throws SQLException { return this.delegate.getClientInfo(name); }
@Override public Properties getClientInfo() throws SQLException { return this.delegate.getClientInfo(); }
@Override public Array createArrayOf(String typeName, Object[] elements) throws SQLException { return this.delegate.createArrayOf(typeName, elements); }
@Override public Struct createStruct(String typeName, Object[] attributes) throws SQLException { return this.delegate.createStruct(typeName, attributes); }
@Override public void setSchema(String schema) throws SQLException { this.delegate.setSchema(schema); }
@Override public String getSchema() throws SQLException { return this.delegate.getSchema(); }
@Override public void abort(Executor executor) throws SQLException { this.delegate.abort(executor); }
@Override public void setNetworkTimeout(Executor executor, int milliseconds) throws SQLException { this.delegate.setNetworkTimeout(executor, milliseconds); }
@Override public int getNetworkTimeout() throws SQLException { return this.delegate.getNetworkTimeout(); }
@Override
public boolean getAutoCommit() throws SQLException {
return this.delegate.getAutoCommit();
}
@Override
public void commit() throws SQLException {
this.delegate.commit();
}
@Override
public void rollback() throws SQLException {
this.delegate.rollback();
}
@Override
public boolean isClosed() throws SQLException {
return this.delegate.isClosed();
}
@Override
public DatabaseMetaData getMetaData() throws SQLException {
return this.delegate.getMetaData();
}
@Override
public void setReadOnly(boolean readOnly) throws SQLException {
this.delegate.setReadOnly(readOnly);
}
@Override
public boolean isReadOnly() throws SQLException {
return this.delegate.isReadOnly();
}
@Override
public void setCatalog(String catalog) throws SQLException {
this.delegate.setCatalog(catalog);
}
@Override
public String getCatalog() throws SQLException {
return this.delegate.getCatalog();
}
@Override
public void setTransactionIsolation(int level) throws SQLException {
this.delegate.setTransactionIsolation(level);
}
@Override
public int getTransactionIsolation() throws SQLException {
return this.delegate.getTransactionIsolation();
}
@Override
public SQLWarning getWarnings() throws SQLException {
return this.delegate.getWarnings();
}
@Override
public void clearWarnings() throws SQLException {
this.delegate.clearWarnings();
}
@Override
public Statement createStatement(int resultSetType, int resultSetConcurrency)
throws SQLException {
return this.delegate.createStatement(resultSetType, resultSetConcurrency);
}
@Override
public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency)
throws SQLException {
return this.delegate.prepareStatement(sql, resultSetType, resultSetConcurrency);
}
@Override
public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency)
throws SQLException {
return this.delegate.prepareCall(sql, resultSetType, resultSetConcurrency);
}
@Override
public Map<String, Class<?>> getTypeMap() throws SQLException {
return this.delegate.getTypeMap();
}
@Override
public void setTypeMap(Map<String, Class<?>> map) throws SQLException {
this.delegate.setTypeMap(map);
}
@Override
public void setHoldability(int holdability) throws SQLException {
this.delegate.setHoldability(holdability);
}
@Override
public int getHoldability() throws SQLException {
return this.delegate.getHoldability();
}
@Override
public Savepoint setSavepoint() throws SQLException {
return this.delegate.setSavepoint();
}
@Override
public Savepoint setSavepoint(String name) throws SQLException {
return this.delegate.setSavepoint(name);
}
@Override
public void rollback(Savepoint savepoint) throws SQLException {
this.delegate.rollback(savepoint);
}
@Override
public void releaseSavepoint(Savepoint savepoint) throws SQLException {
this.delegate.releaseSavepoint(savepoint);
}
@Override
public Statement createStatement(
int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException {
return this.delegate.createStatement(resultSetType, resultSetConcurrency, resultSetHoldability);
}
@Override
public PreparedStatement prepareStatement(
String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability)
throws SQLException {
return this.delegate.prepareStatement(
sql, resultSetType, resultSetConcurrency, resultSetHoldability);
}
@Override
public CallableStatement prepareCall(
String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability)
throws SQLException {
return this.delegate.prepareCall(
sql, resultSetType, resultSetConcurrency, resultSetHoldability);
}
@Override
public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) throws SQLException {
return this.delegate.prepareStatement(sql, autoGeneratedKeys);
}
@Override
public PreparedStatement prepareStatement(String sql, int[] columnIndexes) throws SQLException {
return this.delegate.prepareStatement(sql, columnIndexes);
}
@Override
public PreparedStatement prepareStatement(String sql, String[] columnNames) throws SQLException {
return this.delegate.prepareStatement(sql, columnNames);
}
@Override
public Clob createClob() throws SQLException {
return this.delegate.createClob();
}
@Override
public Blob createBlob() throws SQLException {
return this.delegate.createBlob();
}
@Override
public NClob createNClob() throws SQLException {
return this.delegate.createNClob();
}
@Override
public SQLXML createSQLXML() throws SQLException {
return this.delegate.createSQLXML();
}
@Override
public boolean isValid(int timeout) throws SQLException {
return this.delegate.isValid(timeout);
}
@Override
public void setClientInfo(String name, String value) throws SQLClientInfoException {
this.delegate.setClientInfo(name, value);
}
@Override
public void setClientInfo(Properties properties) throws SQLClientInfoException {
this.delegate.setClientInfo(properties);
}
@Override
public String getClientInfo(String name) throws SQLException {
return this.delegate.getClientInfo(name);
}
@Override
public Properties getClientInfo() throws SQLException {
return this.delegate.getClientInfo();
}
@Override
public Array createArrayOf(String typeName, Object[] elements) throws SQLException {
return this.delegate.createArrayOf(typeName, elements);
}
@Override
public Struct createStruct(String typeName, Object[] attributes) throws SQLException {
return this.delegate.createStruct(typeName, attributes);
}
@Override
public void setSchema(String schema) throws SQLException {
this.delegate.setSchema(schema);
}
@Override
public String getSchema() throws SQLException {
return this.delegate.getSchema();
}
@Override
public void abort(Executor executor) throws SQLException {
this.delegate.abort(executor);
}
@Override
public void setNetworkTimeout(Executor executor, int milliseconds) throws SQLException {
this.delegate.setNetworkTimeout(executor, milliseconds);
}
@Override
public int getNetworkTimeout() throws SQLException {
return this.delegate.getNetworkTimeout();
}
} }
@@ -16,20 +16,24 @@
package dev.brighten.antivpn.database.sql.utils; package dev.brighten.antivpn.database.sql.utils;
import java.sql.Connection;
import java.sql.SQLException;
import lombok.Getter; import lombok.Getter;
import org.intellij.lang.annotations.Language; import org.intellij.lang.annotations.Language;
import java.sql.Connection;
import java.sql.SQLException;
public class Query { public class Query {
@Getter private static Connection conn; @Getter
private static Connection conn;
public static void use(Connection conn) {
Query.conn = conn;
}
@SuppressWarnings("SqlSourceToSinkFlow")
public static ExecutableStatement prepare(@Language("SQL") String sql) throws SQLException {
return new ExecutableStatement(conn.prepareStatement(sql));
}
public static void use(Connection conn) {
Query.conn = conn;
}
@SuppressWarnings("SqlSourceToSinkFlow")
public static ExecutableStatement prepare(@Language("SQL") String sql) throws SQLException {
return new ExecutableStatement(conn.prepareStatement(sql));
}
} }
@@ -20,5 +20,5 @@ import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
public interface ResultSetIterator { public interface ResultSetIterator {
void next(ResultSet rs) throws SQLException; void next(ResultSet rs) throws SQLException;
} }
@@ -21,34 +21,31 @@ import dev.brighten.antivpn.database.DatabaseException;
import dev.brighten.antivpn.database.VPNDatabase; import dev.brighten.antivpn.database.VPNDatabase;
import dev.brighten.antivpn.database.local.version.First; import dev.brighten.antivpn.database.local.version.First;
import dev.brighten.antivpn.database.sql.utils.Query; import dev.brighten.antivpn.database.sql.utils.Query;
import java.sql.SQLException; import java.sql.SQLException;
public class MySQLFirst extends First { public class MySQLFirst extends First {
@Override @Override
public void update(VPNDatabase database) throws DatabaseException { public void update(VPNDatabase database) throws DatabaseException {
try (var statement = try(var statement = Query.prepare("select `DATA_TYPE` from INFORMATION_SCHEMA.COLUMNS " +
Query.prepare( "WHERE table_name = 'responses' AND COLUMN_NAME = 'isp';")) {
"select `DATA_TYPE` from INFORMATION_SCHEMA.COLUMNS " statement.execute(set -> {
+ "WHERE table_name = 'responses' AND COLUMN_NAME = 'isp';")) { if(set.getObject("DATA_TYPE").toString().contains("varchar")) {
statement.execute( AntiVPN.getInstance().getExecutor().log("Using old database format for storing responses! " +
set -> { "Dropping table and creating a new one...");
if (set.getObject("DATA_TYPE").toString().contains("varchar")) { try(var state = Query.prepare("drop table `responses`")) {
AntiVPN.getInstance() if(state.execute() > 0) {
.getExecutor() AntiVPN.getInstance().getExecutor().log("Successfully dropped table!");
.log( }
"Using old database format for storing responses! " }
+ "Dropping table and creating a new one...");
try (var state = Query.prepare("drop table `responses`")) {
if (state.execute() > 0) {
AntiVPN.getInstance().getExecutor().log("Successfully dropped table!");
} }
} });
} } catch (SQLException e) {
}); throw new DatabaseException("Could not update MySQL database", e);
} catch (SQLException e) { }
throw new DatabaseException("Could not update MySQL database", e); super.update(database);
} }
super.update(database);
}
} }
@@ -28,15 +28,13 @@ import dev.brighten.antivpn.database.mongo.version.MongoThird;
import dev.brighten.antivpn.database.sql.MySqlVPN; import dev.brighten.antivpn.database.sql.MySqlVPN;
import dev.brighten.antivpn.database.sql.version.MySQLFirst; import dev.brighten.antivpn.database.sql.version.MySQLFirst;
public interface Version<DB> { public interface Version<DB> {
void update(DB database) throws DatabaseException; void update(DB database) throws DatabaseException;
int versionNumber();
boolean needsUpdate(DB database);
int versionNumber(); Version<MongoVPN>[] mongoDbVersions = new Version[] {new MongoFirst(), new MongoSecond(), new MongoThird()};
Version<MySqlVPN>[] mysqlVersions = new Version[] {new MySQLFirst(), new Second(), new Third()};
boolean needsUpdate(DB database); Version<H2VPN>[] h2Versions = new Version[] {new First(), new Second(), new Third()};
Version<MongoVPN>[] mongoDbVersions =
new Version[] {new MongoFirst(), new MongoSecond(), new MongoThird()};
Version<MySqlVPN>[] mysqlVersions = new Version[] {new MySQLFirst(), new Second(), new Third()};
Version<H2VPN>[] h2Versions = new Version[] {new First(), new Second(), new Third()};
} }
File diff suppressed because it is too large Load Diff
@@ -18,11 +18,14 @@ package dev.brighten.antivpn.depends;
import java.lang.annotation.*; import java.lang.annotation.*;
/** Annotation to indicate the required libraries for a class. */ /**
* Annotation to indicate the required libraries for a class.
*/
@Documented @Documented
@Target(ElementType.TYPE) @Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
public @interface MavenLibraries { public @interface MavenLibraries {
MavenLibrary[] value() default {}; MavenLibrary[] value() default {};
} }
@@ -18,40 +18,43 @@ package dev.brighten.antivpn.depends;
import java.lang.annotation.*; import java.lang.annotation.*;
/** Annotation to indicate a required library for a class. */ /**
* Annotation to indicate a required library for a class.
*/
@Documented @Documented
@Repeatable(MavenLibraries.class) @Repeatable(MavenLibraries.class)
@Target(ElementType.TYPE) @Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
public @interface MavenLibrary { public @interface MavenLibrary {
/** /**
* The group id of the library * The group id of the library
* *
* @return the group id of the library * @return the group id of the library
*/ */
String groupId(); String groupId();
/** /**
* The artifact id of the library * The artifact id of the library
* *
* @return the artifact id of the library * @return the artifact id of the library
*/ */
String artifactId(); String artifactId();
/** /**
* The version of the library * The version of the library
* *
* @return the version of the library * @return the version of the library
*/ */
String version(); String version();
/** /**
* The repo where the library can be obtained from * The repo where the library can be obtained from
* *
* @return the repo where the library can be obtained from * @return the repo where the library can be obtained from
*/ */
Repository repo() default @Repository(url = "https://repo1.maven.org/maven2"); Repository repo() default @Repository(url = "https://repo1.maven.org/maven2");
Relocate[] relocations() default {}; // Add this line
Relocate[] relocations() default {}; // Add this line
} }
@@ -25,7 +25,6 @@ import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@Target({}) @Target({})
public @interface Relocate { public @interface Relocate {
String from(); String from();
String to();
String to();
} }
@@ -18,16 +18,19 @@ package dev.brighten.antivpn.depends;
import java.lang.annotation.*; import java.lang.annotation.*;
/** Represents a maven repository. */ /**
* Represents a maven repository.
*/
@Documented @Documented
@Target(ElementType.LOCAL_VARIABLE) @Target(ElementType.LOCAL_VARIABLE)
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
public @interface Repository { public @interface Repository {
/** /**
* Gets the base url of the repository. * Gets the base url of the repository.
* *
* @return the base url of the repository * @return the base url of the repository
*/ */
String url(); String url();
} }
@@ -22,140 +22,145 @@ import java.net.URL;
import java.net.URLClassLoader; import java.net.URLClassLoader;
import java.util.Collection; import java.util.Collection;
/** Provides access to {@link URLClassLoader}#addURL. */ /**
* Provides access to {@link URLClassLoader}#addURL.
*/
public abstract class URLClassLoaderAccess { public abstract class URLClassLoaderAccess {
/** /**
* Creates a {@link URLClassLoaderAccess} for the given class loader. * Creates a {@link URLClassLoaderAccess} for the given class loader.
* *
* @param classLoader the class loader * @param classLoader the class loader
* @return the access object * @return the access object
*/ */
static URLClassLoaderAccess create(URLClassLoader classLoader) { static URLClassLoaderAccess create(URLClassLoader classLoader) {
if (Reflection.isSupported()) { if (Reflection.isSupported()) {
return new Reflection(classLoader); return new Reflection(classLoader);
} else if (Unsafe.isSupported()) { } else if (Unsafe.isSupported()) {
return new Unsafe(classLoader); return new Unsafe(classLoader);
} else { } else {
return Noop.INSTANCE; return Noop.INSTANCE;
} }
}
private final URLClassLoader classLoader;
protected URLClassLoaderAccess(URLClassLoader classLoader) {
this.classLoader = classLoader;
}
/**
* Adds the given URL to the class loader.
*
* @param url the URL to add
*/
public abstract void addURL(URL url);
/** Accesses using reflection, not supported on Java 9+. */
private static class Reflection extends URLClassLoaderAccess {
private static final Method ADD_URL_METHOD;
static {
Method addUrlMethod;
try {
addUrlMethod = URLClassLoader.class.getDeclaredMethod("addURL", URL.class);
addUrlMethod.setAccessible(true);
} catch (Exception e) {
addUrlMethod = null;
}
ADD_URL_METHOD = addUrlMethod;
} }
private static boolean isSupported() { private final URLClassLoader classLoader;
return ADD_URL_METHOD != null;
protected URLClassLoaderAccess(URLClassLoader classLoader) {
this.classLoader = classLoader;
} }
Reflection(URLClassLoader classLoader) {
super(classLoader); /**
* Adds the given URL to the class loader.
*
* @param url the URL to add
*/
public abstract void addURL(URL url);
/**
* Accesses using reflection, not supported on Java 9+.
*/
private static class Reflection extends URLClassLoaderAccess {
private static final Method ADD_URL_METHOD;
static {
Method addUrlMethod;
try {
addUrlMethod = URLClassLoader.class.getDeclaredMethod("addURL", URL.class);
addUrlMethod.setAccessible(true);
} catch (Exception e) {
addUrlMethod = null;
}
ADD_URL_METHOD = addUrlMethod;
}
private static boolean isSupported() {
return ADD_URL_METHOD != null;
}
Reflection(URLClassLoader classLoader) {
super(classLoader);
}
@Override
public void addURL(URL url) {
try {
ADD_URL_METHOD.invoke(super.classLoader, url);
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
}
} }
@Override /**
public void addURL(URL url) { * Accesses using sun.misc.Unsafe, supported on Java 9+.
try { *
ADD_URL_METHOD.invoke(super.classLoader, url); * @author Vaishnav Anil (<a href="https://github.com/slimjar/slimjar">...</a>)
} catch (ReflectiveOperationException e) { */
throw new RuntimeException(e); private static class Unsafe extends URLClassLoaderAccess {
} private static final sun.misc.Unsafe UNSAFE;
}
}
/** static {
* Accesses using sun.misc.Unsafe, supported on Java 9+. sun.misc.Unsafe unsafe;
* try {
* @author Vaishnav Anil (<a href="https://github.com/slimjar/slimjar">...</a>) Field unsafeField = sun.misc.Unsafe.class.getDeclaredField("theUnsafe");
*/ unsafeField.setAccessible(true);
private static class Unsafe extends URLClassLoaderAccess { unsafe = (sun.misc.Unsafe) unsafeField.get(null);
private static final sun.misc.Unsafe UNSAFE; } catch (Throwable t) {
unsafe = null;
}
UNSAFE = unsafe;
}
static { private static boolean isSupported() {
sun.misc.Unsafe unsafe; return UNSAFE != null;
try { }
Field unsafeField = sun.misc.Unsafe.class.getDeclaredField("theUnsafe");
unsafeField.setAccessible(true); private final Collection<URL> unopenedURLs;
unsafe = (sun.misc.Unsafe) unsafeField.get(null); private final Collection<URL> pathURLs;
} catch (Throwable t) {
unsafe = null; @SuppressWarnings("unchecked")
} Unsafe(URLClassLoader classLoader) {
UNSAFE = unsafe; super(classLoader);
Collection<URL> unopenedURLs;
Collection<URL> pathURLs;
try {
Object ucp = fetchField(URLClassLoader.class, classLoader, "ucp");
unopenedURLs = (Collection<URL>) fetchField(ucp.getClass(), ucp, "unopenedUrls");
pathURLs = (Collection<URL>) fetchField(ucp.getClass(), ucp, "path");
} catch (Throwable e) {
unopenedURLs = null;
pathURLs = null;
}
this.unopenedURLs = unopenedURLs;
this.pathURLs = pathURLs;
}
private static Object fetchField(final Class<?> clazz, final Object object, final String name) throws NoSuchFieldException {
Field field = clazz.getDeclaredField(name);
long offset = UNSAFE.objectFieldOffset(field);
return UNSAFE.getObject(object, offset);
}
@Override
public void addURL(URL url) {
this.unopenedURLs.add(url);
this.pathURLs.add(url);
}
} }
private static boolean isSupported() { private static class Noop extends URLClassLoaderAccess {
return UNSAFE != null; private static final Noop INSTANCE = new Noop();
private Noop() {
super(null);
}
@Override
public void addURL(URL url) {
throw new UnsupportedOperationException();
}
} }
private final Collection<URL> unopenedURLs;
private final Collection<URL> pathURLs;
@SuppressWarnings("unchecked")
Unsafe(URLClassLoader classLoader) {
super(classLoader);
Collection<URL> unopenedURLs;
Collection<URL> pathURLs;
try {
Object ucp = fetchField(URLClassLoader.class, classLoader, "ucp");
unopenedURLs = (Collection<URL>) fetchField(ucp.getClass(), ucp, "unopenedUrls");
pathURLs = (Collection<URL>) fetchField(ucp.getClass(), ucp, "path");
} catch (Throwable e) {
unopenedURLs = null;
pathURLs = null;
}
this.unopenedURLs = unopenedURLs;
this.pathURLs = pathURLs;
}
private static Object fetchField(final Class<?> clazz, final Object object, final String name)
throws NoSuchFieldException {
Field field = clazz.getDeclaredField(name);
long offset = UNSAFE.objectFieldOffset(field);
return UNSAFE.getObject(object, offset);
}
@Override
public void addURL(URL url) {
this.unopenedURLs.add(url);
this.pathURLs.add(url);
}
}
private static class Noop extends URLClassLoaderAccess {
private static final Noop INSTANCE = new Noop();
private Noop() {
super(null);
}
@Override
public void addURL(URL url) {
throw new UnsupportedOperationException();
}
}
} }
@@ -17,51 +17,46 @@
package dev.brighten.antivpn.message; package dev.brighten.antivpn.message;
import dev.brighten.antivpn.AntiVPN; import dev.brighten.antivpn.AntiVPN;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.function.Function; import java.util.function.Function;
public class MessageHandler { public class MessageHandler {
private final Map<String, VpnString> messages = new HashMap<>(); private final Map<String, VpnString> messages = new HashMap<>();
public VpnString getString(String key) { public VpnString getString(String key) {
if (!messages.containsKey(key)) { if(!messages.containsKey(key)) {
throw new NullPointerException("There is no VpnString with the key \"" + key + "\""); throw new NullPointerException("There is no VpnString with the key \"" + key + "\"");
}
return messages.get(key);
} }
return messages.get(key); public void reloadStrings() {
} for (VpnString value : messages.values()) {
value.updateString();
public void reloadStrings() { }
for (VpnString value : messages.values()) {
value.updateString();
} }
}
public void clearStrings() { public void clearStrings() {
messages.clear(); messages.clear();
} }
public void addString(VpnString string, Function<VpnString, String> getter) { public void addString(VpnString string, Function<VpnString, String> getter) {
string.setConfigStringGetter(getter); string.setConfigStringGetter(getter);
getter.apply(string); getter.apply(string);
AntiVPN.getInstance().getExecutor().log("Added string " + string.getKey()); AntiVPN.getInstance().getExecutor().log("Added string " + string.getKey());
messages.put(string.getKey(), string); messages.put(string.getKey(), string);
} }
public void initStrings(Function<VpnString, String> getter) { public void initStrings(Function<VpnString, String> getter) {
addString( addString(new VpnString("command-misc-playerRequired",
new VpnString( "&cYou must be a player to execute this command!"), getter);
"command-misc-playerRequired", "&cYou must be a player to execute this command!"), addString(new VpnString("command-alerts-toggled",
getter); "&7Your player proxy notifications have been set to: &e%state%"), getter);
addString( addString(new VpnString("command-reload-complete",
new VpnString( "&aSuccessfully reloaded KauriVPN plugin!"), getter);
"command-alerts-toggled", addString(new VpnString("no-permission", "&cNo permission."), getter);
"&7Your player proxy notifications have been set to: &e%state%"), }
getter);
addString(
new VpnString("command-reload-complete", "&aSuccessfully reloaded KauriVPN plugin!"),
getter);
addString(new VpnString("no-permission", "&cNo permission."), getter);
}
} }
@@ -17,59 +17,58 @@
package dev.brighten.antivpn.message; package dev.brighten.antivpn.message;
import dev.brighten.antivpn.api.APIPlayer; import dev.brighten.antivpn.api.APIPlayer;
import java.util.function.Function;
import lombok.Getter; import lombok.Getter;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.Setter; import lombok.Setter;
import lombok.SneakyThrows; import lombok.SneakyThrows;
import java.util.function.Function;
@Getter @Getter
public class VpnString { public class VpnString {
private final String key;
private final String defaultMessage;
private String message;
@Setter private Function<VpnString, String> configStringGetter;
public VpnString(String key, String defaultMessage) {
this.key = key;
this.defaultMessage = defaultMessage;
}
@SneakyThrows
public void updateString() {
if (configStringGetter == null)
throw new Exception("The configStringGetter for string " + key + " is null!");
message = configStringGetter.apply(this);
}
public String getFormattedMessage(Var<String, Object>... replacements) {
String formatted = configStringGetter.apply(this);
for (Var<String, Object> replacement : replacements) {
formatted =
formatted.replace(
"%" + replacement.getKey() + "%", replacement.getReplacement().toString());
}
return formatted;
}
public void sendMessage(APIPlayer player, Var<String, Object>... replacements) {
String formatted = message;
for (Var<String, Object> replacement : replacements) {
formatted =
formatted.replace(
"%" + replacement.getKey() + "%", replacement.getReplacement().toString());
}
player.sendMessage(formatted);
}
@Getter
@RequiredArgsConstructor
public static class Var<S, O> {
private final String key; private final String key;
private final Object replacement; private final String defaultMessage;
} private String message;
@Setter
private Function<VpnString, String> configStringGetter;
public VpnString(String key, String defaultMessage) {
this.key = key;
this.defaultMessage = defaultMessage;
}
@SneakyThrows
public void updateString() {
if(configStringGetter == null) throw new Exception("The configStringGetter for string " + key + " is null!");
message = configStringGetter.apply(this);
}
public String getFormattedMessage(Var<String, Object>... replacements) {
String formatted = configStringGetter.apply(this);
for (Var<String, Object> replacement : replacements) {
formatted = formatted
.replace("%" + replacement.getKey() + "%", replacement.getReplacement().toString());
}
return formatted;
}
public void sendMessage(APIPlayer player, Var<String, Object>... replacements) {
String formatted = message;
for (Var<String, Object> replacement : replacements) {
formatted = formatted
.replace("%" + replacement.getKey() + "%", replacement.getReplacement().toString());
}
player.sendMessage(formatted);
}
@Getter
@RequiredArgsConstructor
public static class Var<S, O> {
private final String key;
private final Object replacement;
}
} }
@@ -16,106 +16,116 @@
package dev.brighten.antivpn.utils; package dev.brighten.antivpn.utils;
import lombok.Getter;
import java.math.BigInteger; import java.math.BigInteger;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import lombok.Getter;
/** /**
* A class that enables to get an IP range from CIDR specification. It supports both IPv4 and IPv6. * A class that enables to get an IP range from CIDR specification. It supports
* both IPv4 and IPv6.
*/ */
@Getter @Getter
public class CIDRUtils { public class CIDRUtils {
private final String cidr; private final String cidr;
private final InetAddress inetAddress; private final InetAddress inetAddress;
private InetAddress startAddress; private InetAddress startAddress;
private BigInteger startIpInt, endIpInt; private BigInteger startIpInt, endIpInt;
private InetAddress endAddress; private InetAddress endAddress;
private final int prefixLength; private final int prefixLength;
public CIDRUtils(String cidr) throws UnknownHostException {
this.cidr = cidr; public CIDRUtils(String cidr) throws UnknownHostException {
/* split CIDR to address and prefix part */ this.cidr = cidr;
if (this.cidr.contains("/")) {
int index = this.cidr.indexOf('/');
String addressPart = this.cidr.substring(0, index);
String networkPart = this.cidr.substring(index + 1);
inetAddress = InetAddress.getByName(addressPart); /* split CIDR to address and prefix part */
prefixLength = Integer.parseInt(networkPart); if (this.cidr.contains("/")) {
int index = this.cidr.indexOf("/");
String addressPart = this.cidr.substring(0, index);
String networkPart = this.cidr.substring(index + 1);
calculate(); inetAddress = InetAddress.getByName(addressPart);
} else { prefixLength = Integer.parseInt(networkPart);
throw new IllegalArgumentException("not an valid CIDR format!");
}
}
private void calculate() throws UnknownHostException { calculate();
} else {
ByteBuffer maskBuffer; throw new IllegalArgumentException("not an valid CIDR format!");
int targetSize; }
if (inetAddress.getAddress().length == 4) {
maskBuffer = ByteBuffer.allocate(4).putInt(-1);
targetSize = 4;
} else {
maskBuffer = ByteBuffer.allocate(16).putLong(-1L).putLong(-1L);
targetSize = 16;
} }
BigInteger mask = (new BigInteger(1, maskBuffer.array())).not().shiftRight(prefixLength);
ByteBuffer buffer = ByteBuffer.wrap(inetAddress.getAddress()); private void calculate() throws UnknownHostException {
BigInteger ipVal = new BigInteger(1, buffer.array());
BigInteger startIp = ipVal.and(mask); ByteBuffer maskBuffer;
this.startIpInt = startIp; int targetSize;
BigInteger endIp = startIp.add(mask.not()); if (inetAddress.getAddress().length == 4) {
this.endIpInt = endIp; maskBuffer =
ByteBuffer
.allocate(4)
.putInt(-1);
targetSize = 4;
} else {
maskBuffer = ByteBuffer.allocate(16)
.putLong(-1L)
.putLong(-1L);
targetSize = 16;
}
byte[] startIpArr = toBytes(startIp.toByteArray(), targetSize); BigInteger mask = (new BigInteger(1, maskBuffer.array())).not().shiftRight(prefixLength);
byte[] endIpArr = toBytes(endIp.toByteArray(), targetSize);
this.startAddress = InetAddress.getByAddress(startIpArr); ByteBuffer buffer = ByteBuffer.wrap(inetAddress.getAddress());
this.endAddress = InetAddress.getByAddress(endIpArr); BigInteger ipVal = new BigInteger(1, buffer.array());
}
BigInteger startIp = ipVal.and(mask);
this.startIpInt = startIp;
BigInteger endIp = startIp.add(mask.not());
this.endIpInt = endIp;
byte[] startIpArr = toBytes(startIp.toByteArray(), targetSize);
byte[] endIpArr = toBytes(endIp.toByteArray(), targetSize);
this.startAddress = InetAddress.getByAddress(startIpArr);
this.endAddress = InetAddress.getByAddress(endIpArr);
private byte[] toBytes(byte[] array, int targetSize) {
int counter = 0;
List<Byte> newArr = new ArrayList<Byte>();
while (counter < targetSize && (array.length - 1 - counter >= 0)) {
newArr.add(0, array[array.length - 1 - counter]);
counter++;
} }
int size = newArr.size(); private byte[] toBytes(byte[] array, int targetSize) {
for (int i = 0; i < (targetSize - size); i++) { int counter = 0;
List<Byte> newArr = new ArrayList<Byte>();
while (counter < targetSize && (array.length - 1 - counter >= 0)) {
newArr.add(0, array[array.length - 1 - counter]);
counter++;
}
newArr.add(0, (byte) 0); int size = newArr.size();
for (int i = 0; i < (targetSize - size); i++) {
newArr.add(0, (byte) 0);
}
byte[] ret = new byte[newArr.size()];
for (int i = 0; i < newArr.size(); i++) {
ret[i] = newArr.get(i);
}
return ret;
} }
byte[] ret = new byte[newArr.size()]; public boolean isInRange(String ipAddress) throws UnknownHostException {
for (int i = 0; i < newArr.size(); i++) { InetAddress address = InetAddress.getByName(ipAddress);
ret[i] = newArr.get(i); BigInteger start = new BigInteger(1, this.startAddress.getAddress());
BigInteger end = new BigInteger(1, this.endAddress.getAddress());
BigInteger target = new BigInteger(1, address.getAddress());
int st = start.compareTo(target);
int te = target.compareTo(end);
return (st < 0 || st == 0) && (te < 0 || te == 0);
} }
return ret;
}
public boolean isInRange(String ipAddress) throws UnknownHostException {
InetAddress address = InetAddress.getByName(ipAddress);
BigInteger start = new BigInteger(1, this.startAddress.getAddress());
BigInteger end = new BigInteger(1, this.endAddress.getAddress());
BigInteger target = new BigInteger(1, address.getAddress());
int st = start.compareTo(target);
int te = target.compareTo(end);
return (st < 0 || st == 0) && (te < 0 || te == 0);
}
} }
@@ -22,22 +22,23 @@ import lombok.AllArgsConstructor;
@AllArgsConstructor @AllArgsConstructor
public class ConfigDefault<A> { public class ConfigDefault<A> {
private final A defaultValue; private final A defaultValue;
private final String path; private final String path;
private final AntiVPN plugin; private final AntiVPN plugin;
public A get() { public A get() {
if (plugin.getConfig().get(path) != null) return (A) plugin.getConfig().get(path); if(plugin.getConfig().get(path) != null)
else { return (A) plugin.getConfig().get(path);
plugin.getConfig().set(path, defaultValue); else {
plugin.saveConfig(); plugin.getConfig().set(path, defaultValue);
return defaultValue; plugin.saveConfig();
return defaultValue;
}
} }
}
public A set(A value) { public A set(A value) {
plugin.getConfig().set(path, value); plugin.getConfig().set(path, value);
return value; return value;
} }
} }
@@ -16,18 +16,20 @@
package dev.brighten.antivpn.utils; package dev.brighten.antivpn.utils;
import java.util.LinkedHashMap;
import java.util.Map;
import lombok.Getter; import lombok.Getter;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import java.util.LinkedHashMap;
import java.util.Map;
@RequiredArgsConstructor @RequiredArgsConstructor
public class EvictingMap<K, V> extends LinkedHashMap<K, V> { public class EvictingMap<K, V> extends LinkedHashMap<K, V> {
@Getter private final int size; @Getter
private final int size;
@Override @Override
protected boolean removeEldestEntry(Map.Entry<K, V> eldest) { protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
return size() >= size; return size() >= size;
} }
} }
@@ -25,87 +25,85 @@ import java.net.UnknownHostException;
import java.util.Optional; import java.util.Optional;
public class IpUtils { public class IpUtils {
public static Optional<BigDecimal> getIpDecimal(String address) { public static Optional<BigDecimal> getIpDecimal(String address) {
try { try {
InetAddress inet = InetAddress.getByName(address); InetAddress inet = InetAddress.getByName(address);
if (inet instanceof Inet4Address) { if(inet instanceof Inet4Address) {
return Optional.of(BigDecimal.valueOf(ipv4ToLong(address))); return Optional.of(BigDecimal.valueOf(ipv4ToLong(address)));
} } return Optional.of(new BigDecimal(ipv6ToDecimalFormat(address)));
return Optional.of(new BigDecimal(ipv6ToDecimalFormat(address))); } catch(Exception e) {
} catch (Exception e) { return Optional.empty();
return Optional.empty(); }
}
}
public static long ipv4ToLong(String address) {
String[] addrArray = address.split("\\.");
long ipDecimal = 0;
for (int i = 0; i < addrArray.length; i++) {
int power = 3 - i;
ipDecimal += ((Integer.parseInt(addrArray[i]) % 256 * Math.pow(256, power)));
} }
return ipDecimal; public static long ipv4ToLong(String address) {
} String[] addrArray = address.split("\\.");
public static String getIpv4(long ip) { long ipDecimal = 0;
StringBuilder sb = new StringBuilder(15);
for (int i = 0; i < 4; i++) { for (int i = 0; i < addrArray.length; i++) {
sb.insert(0, ip & 0xff);
if (i < 3) { int power = 3 - i;
sb.insert(0, '.'); ipDecimal += ((Integer.parseInt(addrArray[i]) % 256 * Math.pow(256, power)));
} }
ip >>= 8; return ipDecimal;
} }
return sb.toString(); public static String getIpv4(long ip) {
} StringBuilder sb = new StringBuilder(15);
public static boolean isIpv4(BigDecimal ip) { for (int i = 0; i < 4; i++) {
return ip.compareTo(BigDecimal.valueOf(4294967295L)) <= 0; sb.insert(0, ip & 0xff);
}
public static boolean isIpv6(BigDecimal ip) { if (i < 3) {
return ip.compareTo(BigDecimal.valueOf(4294967295L)) > 0; sb.insert(0, '.');
} }
public static boolean isIpv4(String ip) { ip >>= 8;
return ip.matches("^(?:[0-9]{1,3}\\.){3}[0-9]{1,3}$"); }
}
public static boolean isNotIp(String ip) { return sb.toString();
return !isIpv4(ip) && !isIpv6(ip);
}
public static boolean isIpv6(String ip) {
return ip.matches(
"^([0-9a-fA-F]{1,4}:){7}([0-9a-fA-F]{1,4}|:)$|^(([0-9a-fA-F]{1,4}:){0,6}([0-9a-fA-F]{1,4}|:))?(::([0-9a-fA-F]{1,4}:){0,5}([0-9a-fA-F]{1,4}|:))?$");
}
public static String getIpv4(BigDecimal ip) {
try {
return Inet4Address.getByAddress(ip.toBigInteger().toByteArray()).getHostAddress();
} catch (UnknownHostException e) {
return "Error";
} }
}
public static String getIpv6(BigDecimal ip) { public static boolean isIpv4(BigDecimal ip) {
try { return ip.compareTo(BigDecimal.valueOf(4294967295L)) <= 0;
return Inet6Address.getByAddress(ip.toBigInteger().toByteArray()).getHostAddress(); }
} catch (UnknownHostException e) {
return "Error"; public static boolean isIpv6(BigDecimal ip) {
return ip.compareTo(BigDecimal.valueOf(4294967295L)) > 0;
}
public static boolean isIpv4(String ip) {
return ip.matches("^(?:[0-9]{1,3}\\.){3}[0-9]{1,3}$");
}
public static boolean isNotIp(String ip) {
return !isIpv4(ip) && !isIpv6(ip);
}
public static boolean isIpv6(String ip) {
return ip.matches("^([0-9a-fA-F]{1,4}:){7}([0-9a-fA-F]{1,4}|:)$|^(([0-9a-fA-F]{1,4}:){0,6}([0-9a-fA-F]{1,4}|:))?(::([0-9a-fA-F]{1,4}:){0,5}([0-9a-fA-F]{1,4}|:))?$");
}
public static String getIpv4(BigDecimal ip) {
try {
return Inet4Address.getByAddress(ip.toBigInteger().toByteArray()).getHostAddress();
} catch (UnknownHostException e) {
return "Error";
}
}
public static String getIpv6(BigDecimal ip) {
try {
return Inet6Address.getByAddress(ip.toBigInteger().toByteArray()).getHostAddress();
} catch (UnknownHostException e) {
return "Error";
}
}
public static BigInteger ipv6ToDecimalFormat(String ipAddress) throws UnknownHostException {
return new BigInteger(1, Inet6Address.getByName(ipAddress).getAddress());
} }
}
public static BigInteger ipv6ToDecimalFormat(String ipAddress) throws UnknownHostException {
return new BigInteger(1, Inet6Address.getByName(ipAddress).getAddress());
}
} }
@@ -20,6 +20,7 @@ import dev.brighten.antivpn.AntiVPN;
import dev.brighten.antivpn.utils.json.JSONException; import dev.brighten.antivpn.utils.json.JSONException;
import dev.brighten.antivpn.utils.json.JSONObject; import dev.brighten.antivpn.utils.json.JSONObject;
import dev.brighten.antivpn.utils.json.JsonReader; import dev.brighten.antivpn.utils.json.JsonReader;
import java.io.*; import java.io.*;
import java.math.BigInteger; import java.math.BigInteger;
import java.net.*; import java.net.*;
@@ -31,177 +32,164 @@ import java.util.regex.Pattern;
public class MiscUtils { public class MiscUtils {
private static final Pattern ipv4 = private static final Pattern ipv4 = Pattern.compile("[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}");
Pattern.compile("[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}"); private static final String DEFAULT_FUNKEMUNKY_UUID_ENDPOINT = "https://funkemunky.cc/mojang/uuid?name=";
private static final String DEFAULT_FUNKEMUNKY_UUID_ENDPOINT = private static final String DEFAULT_MOJANG_UUID_ENDPOINT = "https://api.mojang.com/users/profiles/minecraft/";
"https://funkemunky.cc/mojang/uuid?name="; private static volatile String funkemunkyUuidEndpoint = DEFAULT_FUNKEMUNKY_UUID_ENDPOINT;
private static final String DEFAULT_MOJANG_UUID_ENDPOINT = private static volatile String mojangUuidEndpoint = DEFAULT_MOJANG_UUID_ENDPOINT;
"https://api.mojang.com/users/profiles/minecraft/";
private static volatile String funkemunkyUuidEndpoint = DEFAULT_FUNKEMUNKY_UUID_ENDPOINT;
private static volatile String mojangUuidEndpoint = DEFAULT_MOJANG_UUID_ENDPOINT;
public static void close(Closeable... closeables) { public static void close(Closeable... closeables) {
try { try {
for (Closeable closeable : closeables) if (closeable != null) closeable.close(); for (Closeable closeable : closeables) if (closeable != null) closeable.close();
} catch (Exception e) { } catch (Exception e) {
AntiVPN.getInstance().getExecutor().logException(e); AntiVPN.getInstance().getExecutor().logException(e);
} }
}
public static void close(AutoCloseable... closeables) {
try {
for (AutoCloseable closeable : closeables) if (closeable != null) closeable.close();
} catch (Exception e) {
AntiVPN.getInstance().getExecutor().logException(e);
}
}
public static void copy(InputStream in, File file) {
try {
OutputStream out = new FileOutputStream(file);
int lenght;
byte[] buf = new byte[1024];
while ((lenght = in.read(buf)) > 0) {
out.write(buf, 0, lenght);
}
out.close();
in.close();
} catch (Exception e) {
AntiVPN.getInstance().getExecutor().logException(e);
}
}
public static ThreadFactory createThreadFactory(String threadName) {
return r -> {
Thread thread = new Thread(r);
thread.setName(threadName);
return thread;
};
}
public static List<CIDRUtils> rangeToCidrs(BigInteger start, BigInteger end)
throws UnknownHostException {
List<CIDRUtils> cidrs = new ArrayList<>();
while (start.compareTo(end) <= 0) {
// Find the number of trailing zero bits — this determines max block size alignment
int trailingZeros =
start.equals(BigInteger.ZERO)
? 128 // handle the edge case
: start.getLowestSetBit();
// Find the largest block that fits
BigInteger remaining = end.subtract(start).add(BigInteger.ONE);
int maxBits = remaining.bitLength() - 1;
int blockBits = Math.min(trailingZeros, maxBits);
int prefixLen = 32 - blockBits; // use 128 for IPv6
// Build the CIDR string
byte[] addrBytes = toFixedLengthBytes(start); // use 16 for IPv6
String cidr = InetAddress.getByAddress(addrBytes).getHostAddress() + "/" + prefixLen;
cidrs.add(new CIDRUtils(cidr));
// Advance past this block
start = start.add(BigInteger.ONE.shiftLeft(blockBits));
} }
return cidrs; public static void close(AutoCloseable... closeables) {
} try {
for (AutoCloseable closeable : closeables) if (closeable != null) closeable.close();
private static byte[] toFixedLengthBytes(BigInteger value) { } catch (Exception e) {
byte[] raw = value.toByteArray(); AntiVPN.getInstance().getExecutor().logException(e);
byte[] result = new byte[4]; }
int srcPos = Math.max(0, raw.length - 4);
int destPos = Math.max(0, 4 - raw.length);
System.arraycopy(raw, srcPos, result, destPos, Math.min(raw.length, 4));
return result;
}
public static UUID lookupUUID(String playername) {
try {
UUID uuid = lookupUuidFromUrl(funkemunkyUuidEndpoint + playername);
if (uuid != null) {
return uuid;
}
} catch (IOException | JSONException | URISyntaxException e) {
AntiVPN.getInstance()
.getExecutor()
.logException(
"Error while looking up UUID for " + playername + "! Falling back to Mojang API", e);
return lookupMojangUuid(playername);
} }
return null; public static void copy(InputStream in, File file) {
} try {
OutputStream out = new FileOutputStream(file);
int lenght;
byte[] buf = new byte[1024];
private static UUID lookupUuidFromUrl(String url) while ((lenght = in.read(buf)) > 0)
throws IOException, JSONException, URISyntaxException { {
HttpURLConnection connection = (HttpURLConnection) new URI(url).toURL().openConnection(); out.write(buf, 0, lenght);
connection.setConnectTimeout(5000); }
connection.setReadTimeout(5000);
connection.setInstanceFollowRedirects(true);
int responseCode = connection.getResponseCode(); out.close();
if (responseCode >= 500) { in.close();
throw new IOException("Server returned HTTP " + responseCode + " for " + url); } catch (Exception e) {
} AntiVPN.getInstance().getExecutor().logException(e);
if (responseCode != HttpURLConnection.HTTP_OK) { }
return null;
} }
try (InputStream inputStream = connection.getInputStream()) { public static ThreadFactory createThreadFactory(String threadName) {
JSONObject object = return r -> {
new JSONObject( Thread thread = new Thread(r);
JsonReader.readAll( thread.setName(threadName);
new InputStreamReader(inputStream, java.nio.charset.StandardCharsets.UTF_8))); return thread;
if (object.has("uuid")) { };
return parseUuid(object.getString("uuid"));
}
if (object.has("id")) {
return parseUuid(object.getString("id"));
}
} }
return null; public static List<CIDRUtils> rangeToCidrs(BigInteger start, BigInteger end) throws UnknownHostException {
} List<CIDRUtils> cidrs = new ArrayList<>();
private static UUID parseUuid(String value) { while (start.compareTo(end) <= 0) {
if (value.length() == 32) { // Find the number of trailing zero bits — this determines max block size alignment
value = int trailingZeros = start.equals(BigInteger.ZERO)
value.replaceFirst( ? 128 // handle the edge case
"([0-9a-fA-F]{8})([0-9a-fA-F]{4})([0-9a-fA-F]{4})([0-9a-fA-F]{4})([0-9a-fA-F]{12})", : start.getLowestSetBit();
"$1-$2-$3-$4-$5");
// Find the largest block that fits
BigInteger remaining = end.subtract(start).add(BigInteger.ONE);
int maxBits = remaining.bitLength() - 1;
int blockBits = Math.min(trailingZeros, maxBits);
int prefixLen = 32 - blockBits; // use 128 for IPv6
// Build the CIDR string
byte[] addrBytes = toFixedLengthBytes(start); // use 16 for IPv6
String cidr = InetAddress.getByAddress(addrBytes).getHostAddress() + "/" + prefixLen;
cidrs.add(new CIDRUtils(cidr));
// Advance past this block
start = start.add(BigInteger.ONE.shiftLeft(blockBits));
}
return cidrs;
} }
return UUID.fromString(value); private static byte[] toFixedLengthBytes(BigInteger value) {
} byte[] raw = value.toByteArray();
byte[] result = new byte[4];
private static UUID lookupMojangUuid(String playerName) { int srcPos = Math.max(0, raw.length - 4);
try { int destPos = Math.max(0, 4 - raw.length);
return lookupUuidFromUrl(mojangUuidEndpoint + playerName); System.arraycopy(raw, srcPos, result, destPos, Math.min(raw.length, 4));
} catch (IOException | JSONException | URISyntaxException e) { return result;
AntiVPN.getInstance()
.getExecutor()
.logException("Error while looking up UUID for " + playerName + " from Mojang!:", e);
} }
return null; public static UUID lookupUUID(String playername) {
} try {
UUID uuid = lookupUuidFromUrl(funkemunkyUuidEndpoint + playername);
if (uuid != null) {
return uuid;
}
} catch (IOException | JSONException | URISyntaxException e) {
AntiVPN.getInstance().getExecutor().logException("Error while looking up UUID for " + playername + "! Falling back to Mojang API", e);
return lookupMojangUuid(playername);
}
static void setLookupEndpointsForTesting(String funkemunkyEndpoint, String mojangEndpoint) { return null;
funkemunkyUuidEndpoint = funkemunkyEndpoint; }
mojangUuidEndpoint = mojangEndpoint;
}
static void resetLookupEndpointsForTesting() { private static UUID lookupUuidFromUrl(String url) throws IOException, JSONException, URISyntaxException {
funkemunkyUuidEndpoint = DEFAULT_FUNKEMUNKY_UUID_ENDPOINT; HttpURLConnection connection = (HttpURLConnection) new URI(url).toURL().openConnection();
mojangUuidEndpoint = DEFAULT_MOJANG_UUID_ENDPOINT; connection.setConnectTimeout(5000);
} connection.setReadTimeout(5000);
connection.setInstanceFollowRedirects(true);
public static boolean isIpv4(String ip) { int responseCode = connection.getResponseCode();
return ipv4.matcher(ip).matches(); if (responseCode >= 500) {
} throw new IOException("Server returned HTTP " + responseCode + " for " + url);
}
if (responseCode != HttpURLConnection.HTTP_OK) {
return null;
}
try (InputStream inputStream = connection.getInputStream()) {
JSONObject object = new JSONObject(JsonReader.readAll(new InputStreamReader(inputStream, java.nio.charset.StandardCharsets.UTF_8)));
if (object.has("uuid")) {
return parseUuid(object.getString("uuid"));
}
if (object.has("id")) {
return parseUuid(object.getString("id"));
}
}
return null;
}
private static UUID parseUuid(String value) {
if (value.length() == 32) {
value = value.replaceFirst(
"([0-9a-fA-F]{8})([0-9a-fA-F]{4})([0-9a-fA-F]{4})([0-9a-fA-F]{4})([0-9a-fA-F]{12})",
"$1-$2-$3-$4-$5"
);
}
return UUID.fromString(value);
}
private static UUID lookupMojangUuid(String playerName) {
try {
return lookupUuidFromUrl(mojangUuidEndpoint + playerName);
} catch (IOException | JSONException | URISyntaxException e) {
AntiVPN.getInstance().getExecutor().logException("Error while looking up UUID for " + playerName + " from Mojang!:", e);
}
return null;
}
static void setLookupEndpointsForTesting(String funkemunkyEndpoint, String mojangEndpoint) {
funkemunkyUuidEndpoint = funkemunkyEndpoint;
mojangUuidEndpoint = mojangEndpoint;
}
static void resetLookupEndpointsForTesting() {
funkemunkyUuidEndpoint = DEFAULT_FUNKEMUNKY_UUID_ENDPOINT;
mojangUuidEndpoint = DEFAULT_MOJANG_UUID_ENDPOINT;
}
public static boolean isIpv4(String ip)
{
return ipv4.matcher(ip).matches();
}
} }
@@ -22,4 +22,6 @@ import java.lang.annotation.RetentionPolicy;
@Documented @Documented
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
public @interface NonnullByDefault {} public @interface NonnullByDefault {
}
@@ -22,241 +22,239 @@
package dev.brighten.antivpn.utils; package dev.brighten.antivpn.utils;
public final class Preconditions { public final class Preconditions {
private Preconditions() {} private Preconditions() {
public static <T> T checkNotNull(T reference) {
if (reference == null) {
throw new NullPointerException();
} else {
return reference;
}
}
public static <T> T checkNotNull(T reference, Object errorMessage) {
if (reference == null) {
throw new NullPointerException(String.valueOf(errorMessage));
} else {
return reference;
}
}
public static <T> T checkNotNull(
T reference, String errorMessageTemplate, Object... errorMessageArgs) {
if (reference == null) {
throw new NullPointerException(format(errorMessageTemplate, errorMessageArgs));
} else {
return reference;
}
}
public static <T> T checkNotNull(T obj, String errorMessageTemplate, char p1) {
if (obj == null) {
throw new NullPointerException(format(errorMessageTemplate, p1));
} else {
return obj;
}
}
public static <T> T checkNotNull(T obj, String errorMessageTemplate, int p1) {
if (obj == null) {
throw new NullPointerException(format(errorMessageTemplate, p1));
} else {
return obj;
}
}
public static <T> T checkNotNull(T obj, String errorMessageTemplate, long p1) {
if (obj == null) {
throw new NullPointerException(format(errorMessageTemplate, p1));
} else {
return obj;
}
}
public static <T> T checkNotNull(T obj, String errorMessageTemplate, Object p1) {
if (obj == null) {
throw new NullPointerException(format(errorMessageTemplate, p1));
} else {
return obj;
}
}
public static <T> T checkNotNull(T obj, String errorMessageTemplate, char p1, char p2) {
if (obj == null) {
throw new NullPointerException(format(errorMessageTemplate, p1, p2));
} else {
return obj;
}
}
public static <T> T checkNotNull(T obj, String errorMessageTemplate, char p1, int p2) {
if (obj == null) {
throw new NullPointerException(format(errorMessageTemplate, p1, p2));
} else {
return obj;
}
}
public static <T> T checkNotNull(T obj, String errorMessageTemplate, char p1, long p2) {
if (obj == null) {
throw new NullPointerException(format(errorMessageTemplate, p1, p2));
} else {
return obj;
}
}
public static <T> T checkNotNull(T obj, String errorMessageTemplate, char p1, Object p2) {
if (obj == null) {
throw new NullPointerException(format(errorMessageTemplate, p1, p2));
} else {
return obj;
}
}
public static <T> T checkNotNull(T obj, String errorMessageTemplate, int p1, char p2) {
if (obj == null) {
throw new NullPointerException(format(errorMessageTemplate, p1, p2));
} else {
return obj;
}
}
public static <T> T checkNotNull(T obj, String errorMessageTemplate, int p1, int p2) {
if (obj == null) {
throw new NullPointerException(format(errorMessageTemplate, p1, p2));
} else {
return obj;
}
}
public static <T> T checkNotNull(T obj, String errorMessageTemplate, int p1, long p2) {
if (obj == null) {
throw new NullPointerException(format(errorMessageTemplate, p1, p2));
} else {
return obj;
}
}
public static <T> T checkNotNull(T obj, String errorMessageTemplate, int p1, Object p2) {
if (obj == null) {
throw new NullPointerException(format(errorMessageTemplate, p1, p2));
} else {
return obj;
}
}
public static <T> T checkNotNull(T obj, String errorMessageTemplate, long p1, char p2) {
if (obj == null) {
throw new NullPointerException(format(errorMessageTemplate, p1, p2));
} else {
return obj;
}
}
public static <T> T checkNotNull(T obj, String errorMessageTemplate, long p1, int p2) {
if (obj == null) {
throw new NullPointerException(format(errorMessageTemplate, p1, p2));
} else {
return obj;
}
}
public static <T> T checkNotNull(T obj, String errorMessageTemplate, long p1, long p2) {
if (obj == null) {
throw new NullPointerException(format(errorMessageTemplate, p1, p2));
} else {
return obj;
}
}
public static <T> T checkNotNull(T obj, String errorMessageTemplate, long p1, Object p2) {
if (obj == null) {
throw new NullPointerException(format(errorMessageTemplate, p1, p2));
} else {
return obj;
}
}
public static <T> T checkNotNull(T obj, String errorMessageTemplate, Object p1, char p2) {
if (obj == null) {
throw new NullPointerException(format(errorMessageTemplate, p1, p2));
} else {
return obj;
}
}
public static <T> T checkNotNull(T obj, String errorMessageTemplate, Object p1, int p2) {
if (obj == null) {
throw new NullPointerException(format(errorMessageTemplate, p1, p2));
} else {
return obj;
}
}
public static <T> T checkNotNull(T obj, String errorMessageTemplate, Object p1, long p2) {
if (obj == null) {
throw new NullPointerException(format(errorMessageTemplate, p1, p2));
} else {
return obj;
}
}
public static <T> T checkNotNull(T obj, String errorMessageTemplate, Object p1, Object p2) {
if (obj == null) {
throw new NullPointerException(format(errorMessageTemplate, p1, p2));
} else {
return obj;
}
}
public static <T> T checkNotNull(
T obj, String errorMessageTemplate, Object p1, Object p2, Object p3) {
if (obj == null) {
throw new NullPointerException(format(errorMessageTemplate, p1, p2, p3));
} else {
return obj;
}
}
public static <T> T checkNotNull(
T obj, String errorMessageTemplate, Object p1, Object p2, Object p3, Object p4) {
if (obj == null) {
throw new NullPointerException(format(errorMessageTemplate, p1, p2, p3, p4));
} else {
return obj;
}
}
static String format(String template, Object... args) {
template = String.valueOf(template);
StringBuilder builder = new StringBuilder(template.length() + 16 * args.length);
int templateStart = 0;
int i;
int placeholderStart;
for (i = 0; i < args.length; templateStart = placeholderStart + 2) {
placeholderStart = template.indexOf("%s", templateStart);
if (placeholderStart == -1) {
break;
}
builder.append(template, templateStart, placeholderStart);
builder.append(args[i++]);
} }
builder.append(template, templateStart, template.length()); public static <T> T checkNotNull(T reference) {
if (i < args.length) { if (reference == null) {
builder.append(" ["); throw new NullPointerException();
builder.append(args[i++]); } else {
return reference;
while (i < args.length) { }
builder.append(", ");
builder.append(args[i++]);
}
builder.append(']');
} }
return builder.toString(); public static <T> T checkNotNull(T reference, Object errorMessage) {
} if (reference == null) {
throw new NullPointerException(String.valueOf(errorMessage));
} else {
return reference;
}
}
public static <T> T checkNotNull(T reference, String errorMessageTemplate, Object... errorMessageArgs) {
if (reference == null) {
throw new NullPointerException(format(errorMessageTemplate, errorMessageArgs));
} else {
return reference;
}
}
public static <T> T checkNotNull(T obj, String errorMessageTemplate, char p1) {
if (obj == null) {
throw new NullPointerException(format(errorMessageTemplate, p1));
} else {
return obj;
}
}
public static <T> T checkNotNull(T obj, String errorMessageTemplate, int p1) {
if (obj == null) {
throw new NullPointerException(format(errorMessageTemplate, p1));
} else {
return obj;
}
}
public static <T> T checkNotNull(T obj, String errorMessageTemplate, long p1) {
if (obj == null) {
throw new NullPointerException(format(errorMessageTemplate, p1));
} else {
return obj;
}
}
public static <T> T checkNotNull(T obj, String errorMessageTemplate, Object p1) {
if (obj == null) {
throw new NullPointerException(format(errorMessageTemplate, p1));
} else {
return obj;
}
}
public static <T> T checkNotNull(T obj, String errorMessageTemplate, char p1, char p2) {
if (obj == null) {
throw new NullPointerException(format(errorMessageTemplate, p1, p2));
} else {
return obj;
}
}
public static <T> T checkNotNull(T obj, String errorMessageTemplate, char p1, int p2) {
if (obj == null) {
throw new NullPointerException(format(errorMessageTemplate, p1, p2));
} else {
return obj;
}
}
public static <T> T checkNotNull(T obj, String errorMessageTemplate, char p1, long p2) {
if (obj == null) {
throw new NullPointerException(format(errorMessageTemplate, p1, p2));
} else {
return obj;
}
}
public static <T> T checkNotNull(T obj, String errorMessageTemplate, char p1, Object p2) {
if (obj == null) {
throw new NullPointerException(format(errorMessageTemplate, p1, p2));
} else {
return obj;
}
}
public static <T> T checkNotNull(T obj, String errorMessageTemplate, int p1, char p2) {
if (obj == null) {
throw new NullPointerException(format(errorMessageTemplate, p1, p2));
} else {
return obj;
}
}
public static <T> T checkNotNull(T obj, String errorMessageTemplate, int p1, int p2) {
if (obj == null) {
throw new NullPointerException(format(errorMessageTemplate, p1, p2));
} else {
return obj;
}
}
public static <T> T checkNotNull(T obj, String errorMessageTemplate, int p1, long p2) {
if (obj == null) {
throw new NullPointerException(format(errorMessageTemplate, p1, p2));
} else {
return obj;
}
}
public static <T> T checkNotNull(T obj, String errorMessageTemplate, int p1, Object p2) {
if (obj == null) {
throw new NullPointerException(format(errorMessageTemplate, p1, p2));
} else {
return obj;
}
}
public static <T> T checkNotNull(T obj, String errorMessageTemplate, long p1, char p2) {
if (obj == null) {
throw new NullPointerException(format(errorMessageTemplate, p1, p2));
} else {
return obj;
}
}
public static <T> T checkNotNull(T obj, String errorMessageTemplate, long p1, int p2) {
if (obj == null) {
throw new NullPointerException(format(errorMessageTemplate, p1, p2));
} else {
return obj;
}
}
public static <T> T checkNotNull(T obj, String errorMessageTemplate, long p1, long p2) {
if (obj == null) {
throw new NullPointerException(format(errorMessageTemplate, p1, p2));
} else {
return obj;
}
}
public static <T> T checkNotNull(T obj, String errorMessageTemplate, long p1, Object p2) {
if (obj == null) {
throw new NullPointerException(format(errorMessageTemplate, p1, p2));
} else {
return obj;
}
}
public static <T> T checkNotNull(T obj, String errorMessageTemplate, Object p1, char p2) {
if (obj == null) {
throw new NullPointerException(format(errorMessageTemplate, p1, p2));
} else {
return obj;
}
}
public static <T> T checkNotNull(T obj, String errorMessageTemplate, Object p1, int p2) {
if (obj == null) {
throw new NullPointerException(format(errorMessageTemplate, p1, p2));
} else {
return obj;
}
}
public static <T> T checkNotNull(T obj, String errorMessageTemplate, Object p1, long p2) {
if (obj == null) {
throw new NullPointerException(format(errorMessageTemplate, p1, p2));
} else {
return obj;
}
}
public static <T> T checkNotNull(T obj, String errorMessageTemplate, Object p1, Object p2) {
if (obj == null) {
throw new NullPointerException(format(errorMessageTemplate, p1, p2));
} else {
return obj;
}
}
public static <T> T checkNotNull(T obj, String errorMessageTemplate, Object p1, Object p2, Object p3) {
if (obj == null) {
throw new NullPointerException(format(errorMessageTemplate, p1, p2, p3));
} else {
return obj;
}
}
public static <T> T checkNotNull(T obj, String errorMessageTemplate, Object p1, Object p2, Object p3, Object p4) {
if (obj == null) {
throw new NullPointerException(format(errorMessageTemplate, p1, p2, p3, p4));
} else {
return obj;
}
}
static String format(String template, Object... args) {
template = String.valueOf(template);
StringBuilder builder = new StringBuilder(template.length() + 16 * args.length);
int templateStart = 0;
int i;
int placeholderStart;
for(i = 0; i < args.length; templateStart = placeholderStart + 2) {
placeholderStart = template.indexOf("%s", templateStart);
if (placeholderStart == -1) {
break;
}
builder.append(template, templateStart, placeholderStart);
builder.append(args[i++]);
}
builder.append(template, templateStart, template.length());
if (i < args.length) {
builder.append(" [");
builder.append(args[i++]);
while(i < args.length) {
builder.append(", ");
builder.append(args[i++]);
}
builder.append(']');
}
return builder.toString();
}
} }
@@ -20,34 +20,31 @@ import dev.brighten.antivpn.api.APIPlayer;
import dev.brighten.antivpn.web.objects.VPNResponse; import dev.brighten.antivpn.web.objects.VPNResponse;
public class StringUtil { public class StringUtil {
public static String line(String color) { public static String line(String color) {
return color + "&m-----------------------------------------------------"; return color + "&m-----------------------------------------------------";
}
public static String line() {
return "&m-----------------------------------------------------";
}
public static String varReplace(String input, APIPlayer player, VPNResponse result) {
return translateAlternateColorCodes(
'&',
input
.replace("%player%", player.getName())
.replace("%reason%", result.getMethod())
.replace("%country%", result.getCountryName())
.replace("%city%", result.getCity()));
}
public static String translateAlternateColorCodes(char altColorChar, String textToTranslate) {
char[] b = textToTranslate.toCharArray();
for (int i = 0; i < b.length - 1; ++i) {
if (b[i] == altColorChar && "0123456789AaBbCcDdEeFfKkLlMmNnOoRr".indexOf(b[i + 1]) > -1) {
b[i] = 167;
b[i + 1] = Character.toLowerCase(b[i + 1]);
}
} }
return new String(b); public static String line() {
} return "&m-----------------------------------------------------";
}
public static String varReplace(String input, APIPlayer player, VPNResponse result) {
return translateAlternateColorCodes('&', input.replace("%player%", player.getName())
.replace("%reason%", result.getMethod())
.replace("%country%", result.getCountryName())
.replace("%city%", result.getCity()));
}
public static String translateAlternateColorCodes(char altColorChar, String textToTranslate) {
char[] b = textToTranslate.toCharArray();
for(int i = 0; i < b.length - 1; ++i) {
if (b[i] == altColorChar && "0123456789AaBbCcDdEeFfKkLlMmNnOoRr".indexOf(b[i + 1]) > -1) {
b[i] = 167;
b[i + 1] = Character.toLowerCase(b[i + 1]);
}
}
return new String(b);
}
} }
@@ -23,5 +23,5 @@ package dev.brighten.antivpn.utils;
@FunctionalInterface @FunctionalInterface
public interface Supplier<T> extends java.util.function.Supplier<T> { public interface Supplier<T> extends java.util.function.Supplier<T> {
T get(); T get();
} }
@@ -16,13 +16,12 @@
package dev.brighten.antivpn.utils; package dev.brighten.antivpn.utils;
import java.io.Serializable;
import static dev.brighten.antivpn.utils.NullnessCasts.uncheckedCastNullableTToT; import static dev.brighten.antivpn.utils.NullnessCasts.uncheckedCastNullableTToT;
import static dev.brighten.antivpn.utils.Preconditions.checkNotNull; import static dev.brighten.antivpn.utils.Preconditions.checkNotNull;
import static java.util.Objects.requireNonNull; import static java.util.Objects.requireNonNull;
import java.io.Serial;
import java.io.Serializable;
/** /**
* Useful suppliers. * Useful suppliers.
* *
@@ -33,114 +32,114 @@ import java.io.Serializable;
* @since 2.0 * @since 2.0
*/ */
public final class Suppliers { public final class Suppliers {
private Suppliers() {} private Suppliers() {}
/** /**
* Returns a supplier which caches the instance retrieved during the first call to {@code get()} * Returns a supplier which caches the instance retrieved during the first call to {@code get()}
* and returns that value on later calls to {@code get()}. See: <a * and returns that value on subsequent calls to {@code get()}. See: <a
* href="http://en.wikipedia.org/wiki/Memoization">memoization</a> * href="http://en.wikipedia.org/wiki/Memoization">memoization</a>
* *
* <p>The returned supplier is thread-safe. The delegate's {@code get()} method will be invoked at * <p>The returned supplier is thread-safe. The delegate's {@code get()} method will be invoked at
* most once unless the underlying {@code get()} throws an exception. The supplier's serialized * most once unless the underlying {@code get()} throws an exception. The supplier's serialized
* form does not contain the cached value, which will be recalculated when {@code get()} is called * form does not contain the cached value, which will be recalculated when {@code get()} is called
* on the reserialized instance. * on the reserialized instance.
* *
* <p>When the underlying delegate throws an exception then this memorizing supplier will keep * <p>When the underlying delegate throws an exception then this memoizing supplier will keep
* delegating calls until it returns valid data. * delegating calls until it returns valid data.
* *
* <p>If {@code delegate} is an instance created by an earlier call to {@code memoize}, it is * <p>If {@code delegate} is an instance created by an earlier call to {@code memoize}, it is
* returned directly. * returned directly.
*/ */
public static <T> Supplier<T> memoize(Supplier<T> delegate) { public static <T> Supplier<T> memoize(Supplier<T> delegate) {
if (delegate instanceof NonSerializableMemoizingSupplier if (delegate instanceof NonSerializableMemoizingSupplier
|| delegate instanceof MemoizingSupplier) { || delegate instanceof MemoizingSupplier) {
return delegate; return delegate;
}
return delegate instanceof Serializable
? new MemoizingSupplier<>(delegate)
: new NonSerializableMemoizingSupplier<>(delegate);
}
static class MemoizingSupplier<T> implements Supplier<T>, Serializable {
final Supplier<T> delegate;
transient volatile boolean initialized;
// "value" does not need to be volatile; visibility piggy-backs
// on volatile read of "initialized".
transient T value;
MemoizingSupplier(Supplier<T> delegate) {
this.delegate = checkNotNull(delegate);
}
@Override
public T get() {
// A 2-field variant of Double Checked Locking.
if (!initialized) {
synchronized (this) {
if (!initialized) {
T t = delegate.get();
value = t;
initialized = true;
return t;
}
} }
} return delegate instanceof Serializable
// This is safe because we checked `initialized.` ? new MemoizingSupplier<>(delegate)
return uncheckedCastNullableTToT(value); : new NonSerializableMemoizingSupplier<>(delegate);
} }
@Override static class MemoizingSupplier<T> implements Supplier<T>, Serializable {
public String toString() { final Supplier<T> delegate;
return "Suppliers.memoize(" transient volatile boolean initialized;
+ (initialized ? "<supplier that returned " + value + ">" : delegate) // "value" does not need to be volatile; visibility piggy-backs
+ ")"; // on volatile read of "initialized".
} transient T value;
@Serial private static final long serialVersionUID = 0; MemoizingSupplier(Supplier<T> delegate) {
} this.delegate = checkNotNull(delegate);
static class NonSerializableMemoizingSupplier<T> implements Supplier<T> {
volatile Supplier<T> delegate;
volatile boolean initialized;
// "value" does not need to be volatile; visibility piggy-backs
// on volatile read of "initialized".
T value;
NonSerializableMemoizingSupplier(Supplier<T> delegate) {
this.delegate = checkNotNull(delegate);
}
@Override
public T get() {
// A 2-field variant of Double Checked Locking.
if (!initialized) {
synchronized (this) {
if (!initialized) {
/*
* requireNonNull is safe because we read and write `delegate` under synchronization.
*
* TODO(cpovirk): To avoid having to check for null, replace `delegate` with a singleton
* `Supplier` that always throws an exception.
*/
T t = requireNonNull(delegate).get();
value = t;
initialized = true;
// Release the delegate to GC.
delegate = null;
return t;
}
} }
}
// This is safe because we checked `initialized.` @Override
return uncheckedCastNullableTToT(value); public T get() {
// A 2-field variant of Double Checked Locking.
if (!initialized) {
synchronized (this) {
if (!initialized) {
T t = delegate.get();
value = t;
initialized = true;
return t;
}
}
}
// This is safe because we checked `initialized.`
return uncheckedCastNullableTToT(value);
}
@Override
public String toString() {
return "Suppliers.memoize("
+ (initialized ? "<supplier that returned " + value + ">" : delegate)
+ ")";
}
private static final long serialVersionUID = 0;
} }
@Override static class NonSerializableMemoizingSupplier<T> implements Supplier<T> {
public String toString() { volatile Supplier<T> delegate;
Supplier<T> delegate = this.delegate; volatile boolean initialized;
return "Suppliers.memoize(" // "value" does not need to be volatile; visibility piggy-backs
+ (delegate == null ? "<supplier that returned " + value + ">" : delegate) // on volatile read of "initialized".
+ ")"; T value;
NonSerializableMemoizingSupplier(Supplier<T> delegate) {
this.delegate = checkNotNull(delegate);
}
@Override
public T get() {
// A 2-field variant of Double Checked Locking.
if (!initialized) {
synchronized (this) {
if (!initialized) {
/*
* requireNonNull is safe because we read and write `delegate` under synchronization.
*
* TODO(cpovirk): To avoid having to check for null, replace `delegate` with a singleton
* `Supplier` that always throws an exception.
*/
T t = requireNonNull(delegate).get();
value = t;
initialized = true;
// Release the delegate to GC.
delegate = null;
return t;
}
}
}
// This is safe because we checked `initialized.`
return uncheckedCastNullableTToT(value);
}
@Override
public String toString() {
Supplier<T> delegate = this.delegate;
return "Suppliers.memoize("
+ (delegate == null ? "<supplier that returned " + value + ">" : delegate)
+ ")";
}
} }
}
} }
@@ -16,4 +16,6 @@
package dev.brighten.antivpn.utils; package dev.brighten.antivpn.utils;
public record Tuple<F, S>(F first, S second) {} public record Tuple<F, S>(F first, S second) {
}
@@ -18,438 +18,506 @@ package dev.brighten.antivpn.utils.config;
import java.util.*; import java.util.*;
public final class Configuration { public final class Configuration
{
private static final char SEPARATOR = '.'; private static final char SEPARATOR = '.';
final Map<String, Object> self; final Map<String, Object> self;
final Map<String, List<String>> comments; final Map<String, List<String>> comments;
private final Configuration defaults; private final Configuration defaults;
public Configuration() { public Configuration()
this(null); {
} this( null );
public Configuration(Configuration defaults) {
this(new LinkedHashMap<String, Object>(), defaults);
}
Configuration(Map<?, ?> map, Configuration defaults) {
this.self = new LinkedHashMap<>();
this.defaults = defaults;
comments = new HashMap<>();
for (Map.Entry<?, ?> entry : map.entrySet()) {
String key = (entry.getKey() == null) ? "null" : entry.getKey().toString();
if (entry.getValue() instanceof Map) {
this.self.put(
key,
new Configuration(
(Map) entry.getValue(), (defaults == null) ? null : defaults.getSection(key)));
} else {
this.self.put(key, entry.getValue());
}
} }
}
public void loadFromString(String contents) { public Configuration(Configuration defaults)
{
this( new LinkedHashMap<String, Object>(), defaults );
}
List<String> list = new ArrayList<>(); Configuration(Map<?, ?> map, Configuration defaults)
Collections.addAll(list, contents.split("\n")); {
this.self = new LinkedHashMap<>();
this.defaults = defaults;
comments = new HashMap<>();
int currentLayer = 0; for ( Map.Entry<?, ?> entry : map.entrySet() )
String currentPath = ""; {
String key = ( entry.getKey() == null ) ? "null" : entry.getKey().toString();
int lineNumber = 0; if ( entry.getValue() instanceof Map )
for (Iterator<String> iterator = list.iterator(); iterator.hasNext(); lineNumber++) { {
String line = iterator.next(); this.self.put( key, new Configuration( (Map) entry.getValue(), ( defaults == null ) ? null : defaults.getSection( key ) ) );
} else
String trimmed = line.trim(); {
if (trimmed.startsWith("#") || trimmed.isEmpty()) { this.self.put( key, entry.getValue() );
addCommentLine(currentPath, line); }
continue;
}
if (!line.isEmpty()) {
if (line.contains(":")) {
int layerFromLine = getLayerFromLine(line, lineNumber);
if (layerFromLine < currentLayer) {
currentPath = regressPathBy(currentLayer - layerFromLine, currentPath);
}
String key = getKeyFromLine(line);
if (currentLayer == 0) {
currentPath = key;
} else {
currentPath += "." + key;
}
} }
}
}
}
private void addCommentLine(String currentPath, String line) {
List<String> list = comments.get(currentPath);
if (list == null) {
list = new ArrayList<>();
}
list.add(line);
comments.put(currentPath, list);
}
String getKeyFromLine(String line) {
String key = null;
for (int i = 0; i < line.length(); i++) {
if (line.charAt(i) == ':') {
key = line.substring(0, i);
break;
}
} }
return key == null ? null : key.trim(); public void loadFromString(String contents) {
}
String regressPathBy(int i, String currentPath) { List<String> list = new ArrayList<>();
if (i <= 0) { Collections.addAll(list, contents.split("\n"));
return currentPath;
}
String[] split = currentPath.split("\\.");
String rebuild = ""; int currentLayer = 0;
for (int j = 0; j < split.length - i; j++) { String currentPath = "";
rebuild += split[j];
if (j <= (split.length - j)) { int lineNumber = 0;
rebuild += "."; for(Iterator<String> iterator = list.iterator(); iterator.hasNext(); lineNumber++) {
} String line = iterator.next();
String trimmed = line.trim();
if(trimmed.startsWith("#") || trimmed.isEmpty()) {
addCommentLine(currentPath, line);
continue;
}
if(!line.isEmpty()) {
if(line.contains(":")) {
int layerFromLine = getLayerFromLine(line, lineNumber);
if(layerFromLine < currentLayer) {
currentPath = regressPathBy(currentLayer - layerFromLine, currentPath);
}
String key = getKeyFromLine(line);
if(currentLayer == 0) {
currentPath = key;
}
else {
currentPath += "." + key;
}
}
}
}
} }
return rebuild; private void addCommentLine(String currentPath, String line) {
}
int getLayerFromLine(String line, int lineNumber) { List<String> list = comments.get(currentPath);
if(list == null) {
list = new ArrayList<>();
}
list.add(line);
double d = 0; comments.put(currentPath, list);
for (int i = 0; i < line.length(); i++) {
if (line.charAt(i) == ' ') {
d += 0.5;
} else {
break;
}
} }
return (int) d; String getKeyFromLine(String line) {
} String key = null;
private Configuration getSectionFor(String path) { for(int i = 0; i < line.length(); i++) {
int index = path.indexOf(SEPARATOR); if(line.charAt(i) == ':') {
if (index == -1) { key = line.substring(0, i);
return this; break;
}
}
return key == null ? null : key.trim();
} }
String root = path.substring(0, index); String regressPathBy(int i, String currentPath) {
Object section = self.get(root); if(i <= 0) {
if (section == null) { return currentPath;
section = new Configuration((defaults == null) ? null : defaults.getSection(root)); }
self.put(root, section); String[] split = currentPath.split("\\.");
String rebuild = "";
for(int j = 0; j < split.length - i; j++) {
rebuild += split[j];
if(j <= (split.length - j)) {
rebuild += ".";
}
}
return rebuild;
} }
return (Configuration) section; int getLayerFromLine(String line, int lineNumber) {
}
private String getChild(String path) { double d = 0;
int index = path.indexOf(SEPARATOR); for(int i = 0; i < line.length(); i++) {
return (index == -1) ? path : path.substring(index + 1); if(line.charAt(i) == ' ') {
} d += 0.5;
}
else {
break;
}
}
return (int) d;
/*------------------------------------------------------------------------*/
@SuppressWarnings("unchecked")
public <T> T get(String path, T def) {
Configuration section = getSectionFor(path);
Object val;
if (section == this) {
val = self.get(path);
} else {
val = section.get(getChild(path), def);
} }
if (val == null && def instanceof Configuration) { private Configuration getSectionFor(String path)
self.put(path, def); {
int index = path.indexOf( SEPARATOR );
if ( index == -1 )
{
return this;
}
String root = path.substring( 0, index );
Object section = self.get( root );
if ( section == null )
{
section = new Configuration( ( defaults == null ) ? null : defaults.getSection( root ) );
self.put( root, section );
}
return (Configuration) section;
} }
return (val != null) ? (T) val : def; private String getChild(String path)
} {
int index = path.indexOf( SEPARATOR );
public boolean contains(String path) { return ( index == -1 ) ? path : path.substring( index + 1 );
return get(path, null) != null;
}
public Object get(String path) {
return get(path, getDefault(path));
}
public Object getDefault(String path) {
return (defaults == null) ? null : defaults.get(path);
}
public void set(String path, Object value) {
if (value instanceof Map) {
value = new Configuration((Map) value, (defaults == null) ? null : defaults.getSection(path));
} }
Configuration section = getSectionFor(path); /*------------------------------------------------------------------------*/
if (section == this) { @SuppressWarnings("unchecked")
if (value == null) { public <T> T get(String path, T def)
self.remove(path); {
} else { Configuration section = getSectionFor( path );
self.put(path, value); Object val;
} if ( section == this )
} else { {
section.set(getChild(path), value); val = self.get( path );
} } else
} {
val = section.get( getChild( path ), def );
}
/*------------------------------------------------------------------------*/ if ( val == null && def instanceof Configuration )
public Configuration getSection(String path) { {
Object def = getDefault(path); self.put( path, def );
return (Configuration) }
get(
path,
(def instanceof Configuration)
? def
: new Configuration((defaults == null) ? null : defaults.getSection(path)));
}
/** return ( val != null ) ? (T) val : def;
* Gets keys, not deep by default.
*
* @return top level keys for this section
*/
public Collection<String> getKeys() {
return new LinkedHashSet<>(self.keySet());
}
/*------------------------------------------------------------------------*/
public byte getByte(String path) {
Object def = getDefault(path);
return getByte(path, (def instanceof Number) ? ((Number) def).byteValue() : 0);
}
public byte getByte(String path, byte def) {
Object val = get(path, def);
return (val instanceof Number) ? ((Number) val).byteValue() : def;
}
public List<Byte> getByteList(String path) {
List<?> list = getList(path);
List<Byte> result = new ArrayList<>();
for (Object object : list) {
if (object instanceof Number) {
result.add(((Number) object).byteValue());
}
} }
return result; public boolean contains(String path)
} {
return get( path, null ) != null;
public short getShort(String path) {
Object def = getDefault(path);
return getShort(path, (def instanceof Number) ? ((Number) def).shortValue() : 0);
}
public short getShort(String path, short def) {
Object val = get(path, def);
return (val instanceof Number) ? ((Number) val).shortValue() : def;
}
public List<Short> getShortList(String path) {
List<?> list = getList(path);
List<Short> result = new ArrayList<>();
for (Object object : list) {
if (object instanceof Number) {
result.add(((Number) object).shortValue());
}
} }
return result; public Object get(String path)
} {
return get( path, getDefault( path ) );
public int getInt(String path) {
Object def = getDefault(path);
return getInt(path, (def instanceof Number) ? ((Number) def).intValue() : 0);
}
public int getInt(String path, int def) {
Object val = get(path, def);
return (val instanceof Number) ? ((Number) val).intValue() : def;
}
public List<Integer> getIntList(String path) {
List<?> list = getList(path);
List<Integer> result = new ArrayList<>();
for (Object object : list) {
if (object instanceof Number) {
result.add(((Number) object).intValue());
}
} }
return result; public Object getDefault(String path)
} {
return ( defaults == null ) ? null : defaults.get( path );
public long getLong(String path) {
Object def = getDefault(path);
return getLong(path, (def instanceof Number) ? ((Number) def).longValue() : 0);
}
public long getLong(String path, long def) {
Object val = get(path, def);
return (val instanceof Number) ? ((Number) val).longValue() : def;
}
public List<Long> getLongList(String path) {
List<?> list = getList(path);
List<Long> result = new ArrayList<>();
for (Object object : list) {
if (object instanceof Number) {
result.add(((Number) object).longValue());
}
} }
return result; public void set(String path, Object value)
} {
if ( value instanceof Map )
{
value = new Configuration( (Map) value, ( defaults == null ) ? null : defaults.getSection( path ) );
}
public float getFloat(String path) { Configuration section = getSectionFor( path );
Object def = getDefault(path); if ( section == this )
return getFloat(path, (def instanceof Number) ? ((Number) def).floatValue() : 0); {
} if ( value == null )
{
public float getFloat(String path, float def) { self.remove( path );
Object val = get(path, def); } else
return (val instanceof Number) ? ((Number) val).floatValue() : def; {
} self.put( path, value );
}
public List<Float> getFloatList(String path) { } else
List<?> list = getList(path); {
List<Float> result = new ArrayList<>(); section.set( getChild( path ), value );
}
for (Object object : list) {
if (object instanceof Number) {
result.add(((Number) object).floatValue());
}
} }
return result; /*------------------------------------------------------------------------*/
} public Configuration getSection(String path)
{
public double getDouble(String path) { Object def = getDefault( path );
Object def = getDefault(path); return (Configuration) get( path, ( def instanceof Configuration ) ? def : new Configuration( ( defaults == null ) ? null : defaults.getSection( path ) ) );
return getDouble(path, (def instanceof Number) ? ((Number) def).doubleValue() : 0);
}
public double getDouble(String path, double def) {
Object val = get(path, def);
return (val instanceof Number) ? ((Number) val).doubleValue() : def;
}
public List<Double> getDoubleList(String path) {
List<?> list = getList(path);
List<Double> result = new ArrayList<>();
for (Object object : list) {
if (object instanceof Number) {
result.add(((Number) object).doubleValue());
}
} }
return result; /**
} * Gets keys, not deep by default.
*
public boolean getBoolean(String path) { * @return top level keys for this section
Object def = getDefault(path); */
return getBoolean(path, (def instanceof Boolean) ? (Boolean) def : false); public Collection<String> getKeys()
} {
return new LinkedHashSet<>( self.keySet() );
public boolean getBoolean(String path, boolean def) {
Object val = get(path, def);
return (val instanceof Boolean) ? (Boolean) val : def;
}
public List<Boolean> getBooleanList(String path) {
List<?> list = getList(path);
List<Boolean> result = new ArrayList<>();
for (Object object : list) {
if (object instanceof Boolean) {
result.add((Boolean) object);
}
} }
return result; /*------------------------------------------------------------------------*/
} public byte getByte(String path)
{
public char getChar(String path) { Object def = getDefault( path );
Object def = getDefault(path); return getByte( path, ( def instanceof Number ) ? ( (Number) def ).byteValue() : 0 );
return getChar(path, (def instanceof Character) ? (Character) def : '\u0000');
}
public char getChar(String path, char def) {
Object val = get(path, def);
return (val instanceof Character) ? (Character) val : def;
}
public List<Character> getCharList(String path) {
List<?> list = getList(path);
List<Character> result = new ArrayList<>();
for (Object object : list) {
if (object instanceof Character) {
result.add((Character) object);
}
} }
return result; public byte getByte(String path, byte def)
} {
Object val = get( path, def );
public String getString(String path) { return ( val instanceof Number ) ? ( (Number) val ).byteValue() : def;
Object def = getDefault(path);
return getString(path, (def instanceof String) ? (String) def : "");
}
public String getString(String path, String def) {
Object val = get(path, def);
return (val instanceof String) ? (String) val : def;
}
public List<String> getStringList(String path) {
List<?> list = getList(path);
List<String> result = new ArrayList<>();
for (Object object : list) {
if (object instanceof String) {
result.add((String) object);
}
} }
return result; public List<Byte> getByteList(String path)
} {
List<?> list = getList( path );
List<Byte> result = new ArrayList<>();
/*------------------------------------------------------------------------*/ for ( Object object : list )
public List<?> getList(String path) { {
Object def = getDefault(path); if ( object instanceof Number )
return getList(path, (def instanceof List<?>) ? (List<?>) def : Collections.EMPTY_LIST); {
} result.add( ( (Number) object ).byteValue() );
}
}
public List<?> getList(String path, List<?> def) { return result;
Object val = get(path, def); }
return (val instanceof List<?>) ? (List<?>) val : def;
} public short getShort(String path)
{
Object def = getDefault( path );
return getShort( path, ( def instanceof Number ) ? ( (Number) def ).shortValue() : 0 );
}
public short getShort(String path, short def)
{
Object val = get( path, def );
return ( val instanceof Number ) ? ( (Number) val ).shortValue() : def;
}
public List<Short> getShortList(String path)
{
List<?> list = getList( path );
List<Short> result = new ArrayList<>();
for ( Object object : list )
{
if ( object instanceof Number )
{
result.add( ( (Number) object ).shortValue() );
}
}
return result;
}
public int getInt(String path)
{
Object def = getDefault( path );
return getInt( path, ( def instanceof Number ) ? ( (Number) def ).intValue() : 0 );
}
public int getInt(String path, int def)
{
Object val = get( path, def );
return ( val instanceof Number ) ? ( (Number) val ).intValue() : def;
}
public List<Integer> getIntList(String path)
{
List<?> list = getList( path );
List<Integer> result = new ArrayList<>();
for ( Object object : list )
{
if ( object instanceof Number )
{
result.add( ( (Number) object ).intValue() );
}
}
return result;
}
public long getLong(String path)
{
Object def = getDefault( path );
return getLong( path, ( def instanceof Number ) ? ( (Number) def ).longValue() : 0 );
}
public long getLong(String path, long def)
{
Object val = get( path, def );
return ( val instanceof Number ) ? ( (Number) val ).longValue() : def;
}
public List<Long> getLongList(String path)
{
List<?> list = getList( path );
List<Long> result = new ArrayList<>();
for ( Object object : list )
{
if ( object instanceof Number )
{
result.add( ( (Number) object ).longValue() );
}
}
return result;
}
public float getFloat(String path)
{
Object def = getDefault( path );
return getFloat( path, ( def instanceof Number ) ? ( (Number) def ).floatValue() : 0 );
}
public float getFloat(String path, float def)
{
Object val = get( path, def );
return ( val instanceof Number ) ? ( (Number) val ).floatValue() : def;
}
public List<Float> getFloatList(String path)
{
List<?> list = getList( path );
List<Float> result = new ArrayList<>();
for ( Object object : list )
{
if ( object instanceof Number )
{
result.add( ( (Number) object ).floatValue() );
}
}
return result;
}
public double getDouble(String path)
{
Object def = getDefault( path );
return getDouble( path, ( def instanceof Number ) ? ( (Number) def ).doubleValue() : 0 );
}
public double getDouble(String path, double def)
{
Object val = get( path, def );
return ( val instanceof Number ) ? ( (Number) val ).doubleValue() : def;
}
public List<Double> getDoubleList(String path)
{
List<?> list = getList( path );
List<Double> result = new ArrayList<>();
for ( Object object : list )
{
if ( object instanceof Number )
{
result.add( ( (Number) object ).doubleValue() );
}
}
return result;
}
public boolean getBoolean(String path)
{
Object def = getDefault( path );
return getBoolean( path, ( def instanceof Boolean ) ? (Boolean) def : false );
}
public boolean getBoolean(String path, boolean def)
{
Object val = get( path, def );
return ( val instanceof Boolean ) ? (Boolean) val : def;
}
public List<Boolean> getBooleanList(String path)
{
List<?> list = getList( path );
List<Boolean> result = new ArrayList<>();
for ( Object object : list )
{
if ( object instanceof Boolean )
{
result.add( (Boolean) object );
}
}
return result;
}
public char getChar(String path)
{
Object def = getDefault( path );
return getChar( path, ( def instanceof Character ) ? (Character) def : '\u0000' );
}
public char getChar(String path, char def)
{
Object val = get( path, def );
return ( val instanceof Character ) ? (Character) val : def;
}
public List<Character> getCharList(String path)
{
List<?> list = getList( path );
List<Character> result = new ArrayList<>();
for ( Object object : list )
{
if ( object instanceof Character )
{
result.add( (Character) object );
}
}
return result;
}
public String getString(String path)
{
Object def = getDefault( path );
return getString( path, ( def instanceof String ) ? (String) def : "" );
}
public String getString(String path, String def)
{
Object val = get( path, def );
return ( val instanceof String ) ? (String) val : def;
}
public List<String> getStringList(String path)
{
List<?> list = getList( path );
List<String> result = new ArrayList<>();
for ( Object object : list )
{
if ( object instanceof String )
{
result.add( (String) object );
}
}
return result;
}
/*------------------------------------------------------------------------*/
public List<?> getList(String path)
{
Object def = getDefault( path );
return getList( path, ( def instanceof List<?> ) ? (List<?>) def : Collections.EMPTY_LIST );
}
public List<?> getList(String path, List<?> def)
{
Object val = get( path, def );
return ( val instanceof List<?> ) ? (List<?>) val : def;
}
} }
@@ -20,42 +20,46 @@ import java.io.*;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
public abstract class ConfigurationProvider { public abstract class ConfigurationProvider
{
public static final Map<Class<? extends ConfigurationProvider>, ConfigurationProvider> providers = public static final Map<Class<? extends ConfigurationProvider>, ConfigurationProvider> providers = new HashMap<>();
new HashMap<>();
static { static
try { {
providers.put(YamlConfiguration.class, new YamlConfiguration()); try
} catch (NoClassDefFoundError ex) { {
ex.printStackTrace(); providers.put( YamlConfiguration.class, new YamlConfiguration() );
// Ignore, no SnakeYAML } catch ( NoClassDefFoundError ex )
{
ex.printStackTrace();
// Ignore, no SnakeYAML
}
} }
}
public static ConfigurationProvider getProvider(Class<? extends ConfigurationProvider> provider) { public static ConfigurationProvider getProvider(Class<? extends ConfigurationProvider> provider)
return providers.get(provider); {
} return providers.get( provider );
}
/*------------------------------------------------------------------------*/ /*------------------------------------------------------------------------*/
public abstract void save(Configuration config, File file) throws IOException; public abstract void save(Configuration config, File file) throws IOException;
public abstract void save(Configuration config, Writer writer); public abstract void save(Configuration config, Writer writer);
public abstract Configuration load(File file) throws IOException; public abstract Configuration load(File file) throws IOException;
public abstract Configuration load(File file, Configuration defaults) throws IOException; public abstract Configuration load(File file, Configuration defaults) throws IOException;
public abstract Configuration load(Reader reader); public abstract Configuration load(Reader reader);
public abstract Configuration load(Reader reader, Configuration defaults); public abstract Configuration load(Reader reader, Configuration defaults);
public abstract Configuration load(InputStream is); public abstract Configuration load(InputStream is);
public abstract Configuration load(InputStream is, Configuration defaults); public abstract Configuration load(InputStream is, Configuration defaults);
public abstract Configuration load(String string); public abstract Configuration load(String string);
public abstract Configuration load(String string, Configuration defaults); public abstract Configuration load(String string, Configuration defaults);
} }
@@ -16,10 +16,6 @@
package dev.brighten.antivpn.utils.config; package dev.brighten.antivpn.utils.config;
import java.io.*;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.*;
import lombok.AccessLevel; import lombok.AccessLevel;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import lombok.SneakyThrows; import lombok.SneakyThrows;
@@ -29,161 +25,173 @@ import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.constructor.Constructor; import org.yaml.snakeyaml.constructor.Constructor;
import org.yaml.snakeyaml.representer.Representer; import org.yaml.snakeyaml.representer.Representer;
import java.io.*;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.*;
@NoArgsConstructor(access = AccessLevel.PACKAGE) @NoArgsConstructor(access = AccessLevel.PACKAGE)
public class YamlConfiguration extends ConfigurationProvider { public class YamlConfiguration extends ConfigurationProvider
{
private final ThreadLocal<Yaml> yaml = private final ThreadLocal<Yaml> yaml = new ThreadLocal<Yaml>()
new ThreadLocal<Yaml>() { {
@Override @Override
protected Yaml initialValue() { protected Yaml initialValue()
DumperOptions options = new DumperOptions(); {
options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK); DumperOptions options = new DumperOptions();
Representer representer = options.setDefaultFlowStyle( DumperOptions.FlowStyle.BLOCK );
new Representer(options) { Representer representer = new Representer(options)
{
{ {
representers.put( representers.put( Configuration.class, data -> represent( ( (Configuration) data ).self ));
Configuration.class, data -> represent(((Configuration) data).self));
} }
}; };
representer.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK); representer.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
return new Yaml(new Constructor(new LoaderOptions()), representer, options); return new Yaml( new Constructor(new LoaderOptions()), representer, options );
} }
}; };
@Override @Override
public void save(Configuration config, File file) throws IOException { public void save(Configuration config, File file) throws IOException
try (Writer writer = {
new OutputStreamWriter(new FileOutputStream(file), StandardCharsets.UTF_8)) { try ( Writer writer = new OutputStreamWriter( new FileOutputStream( file ), StandardCharsets.UTF_8 ) )
save(config, writer); {
} save( config, writer );
}
@Override
public void save(Configuration config, Writer writer) {
String contents = this.yaml.get().dump(config.self);
if (contents.equals("{}\n")) {
contents = "";
}
List<String> list = new ArrayList<>();
Collections.addAll(list, contents.split("\n"));
int currentLayer = 0;
StringBuilder currentPath = new StringBuilder();
StringBuilder sb = new StringBuilder();
int lineNumber = 0;
for (Iterator<String> iterator = list.iterator(); iterator.hasNext(); lineNumber++) {
String line = iterator.next();
sb.append(line);
sb.append('\n');
if (!line.isEmpty()) {
if (line.contains(":")) {
int layerFromLine = config.getLayerFromLine(line, lineNumber);
if (layerFromLine < currentLayer) {
currentPath =
new StringBuilder(
config.regressPathBy(currentLayer - layerFromLine, currentPath.toString()));
}
String key = config.getKeyFromLine(line);
if (currentLayer == 0) {
currentPath = new StringBuilder(key);
} else {
currentPath.append("." + key);
}
String path = currentPath.toString();
if (config.comments.containsKey(path)) {
config
.comments
.get(path)
.forEach(
string -> {
sb.append(string);
sb.append('\n');
});
}
} }
}
} }
try { @Override
writer.write(sb.toString()); public void save(Configuration config, Writer writer)
} catch (IOException e) { {
e.printStackTrace(); String contents = this.yaml.get().dump(config.self);
if (contents.equals("{}\n")) {
contents = "";
}
List<String> list = new ArrayList<>();
Collections.addAll(list, contents.split("\n"));
int currentLayer = 0;
StringBuilder currentPath = new StringBuilder();
StringBuilder sb = new StringBuilder();
int lineNumber = 0;
for(Iterator<String> iterator = list.iterator(); iterator.hasNext(); lineNumber++) {
String line = iterator.next();
sb.append(line);
sb.append('\n');
if (!line.isEmpty()) {
if (line.contains(":")) {
int layerFromLine = config.getLayerFromLine(line, lineNumber);
if (layerFromLine < currentLayer) {
currentPath = new StringBuilder(config.regressPathBy(currentLayer - layerFromLine, currentPath.toString()));
}
String key = config.getKeyFromLine(line);
if (currentLayer == 0) {
currentPath = new StringBuilder(key);
} else {
currentPath.append("." + key);
}
String path = currentPath.toString();
if (config.comments.containsKey(path)) {
config.comments.get(path).forEach(string -> {
sb.append(string);
sb.append('\n');
});
}
}
}
}
try {
writer.write(sb.toString());
} catch (IOException e) {
e.printStackTrace();
}
} }
} @Override
public Configuration load(File file) throws IOException
@Override {
public Configuration load(File file) throws IOException { return load( file, null );
return load(file, null);
}
@Override
public Configuration load(File file, Configuration defaults) throws IOException {
try (FileInputStream is = new FileInputStream(file)) {
return load(is, defaults);
}
}
@Override
public Configuration load(Reader reader) {
return load(reader, null);
}
@SneakyThrows
@Override
public Configuration load(Reader reader, Configuration defaults) {
BufferedReader input =
reader instanceof BufferedReader ? (BufferedReader) reader : new BufferedReader(reader);
StringBuilder builder = new StringBuilder();
String line;
try {
while ((line = input.readLine()) != null) {
builder.append(line);
builder.append('\n');
}
} finally {
input.close();
} }
return load(builder.toString(), defaults); @Override
} public Configuration load(File file, Configuration defaults) throws IOException
{
try ( FileInputStream is = new FileInputStream( file ) )
{
return load( is, defaults );
}
}
@Override @Override
public Configuration load(InputStream is) { public Configuration load(Reader reader)
return this.load(new InputStreamReader(is, Charset.defaultCharset())); {
} return load( reader, null );
}
@Override @SneakyThrows
public Configuration load(InputStream is, Configuration defaults) { @Override
return this.load(new InputStreamReader(is, Charset.defaultCharset()), defaults); public Configuration load(Reader reader, Configuration defaults)
} {
BufferedReader input = reader instanceof BufferedReader ? (BufferedReader)reader : new BufferedReader(reader);
StringBuilder builder = new StringBuilder();
@Override String line;
public Configuration load(String string) { try {
return load(string, null); while((line = input.readLine()) != null) {
} builder.append(line);
builder.append('\n');
}
} finally {
input.close();
}
@Override
@SuppressWarnings("unchecked")
public Configuration load(String contents, Configuration defaults) {
Map<String, Object> map;
LoaderOptions loaderOptions = new LoaderOptions();
loaderOptions.setMaxAliasesForCollections(2147483647);
map = this.yaml.get().loadAs(contents, LinkedHashMap.class);
Configuration config = new Configuration(map, defaults); return load(builder.toString(), defaults);
config.loadFromString(contents); }
@Override
public Configuration load(InputStream is)
{
return this.load(new InputStreamReader(is, Charset.defaultCharset()));
}
@Override
public Configuration load(InputStream is, Configuration defaults)
{
return this.load(new InputStreamReader(is, Charset.defaultCharset()), defaults);
}
@Override
public Configuration load(String string)
{
return load( string, null );
}
@Override
@SuppressWarnings("unchecked")
public Configuration load(String contents, Configuration defaults)
{
Map<String, Object> map;
LoaderOptions loaderOptions = new LoaderOptions();
loaderOptions.setMaxAliasesForCollections(2147483647);
map = this.yaml.get().loadAs(contents, LinkedHashMap.class);
Configuration config = new Configuration( map, defaults );
config.loadFromString(contents);
return config;
}
return config;
}
} }
@@ -17,254 +17,266 @@
package dev.brighten.antivpn.utils.json; package dev.brighten.antivpn.utils.json;
/** /**
* This provides static methods to convert comma delimited text into a JSONArray, and to covert a * This provides static methods to convert comma delimited text into a
* JSONArray into comma delimited text. Comma delimited text is a very popular format for data * JSONArray, and to covert a JSONArray into comma delimited text. Comma
* interchange. It is understood by most database, spreadsheet, and organizer programs. * delimited text is a very popular format for data interchange. It is
* * understood by most database, spreadsheet, and organizer programs.
* <p>Each row of text represents a row in a table or a data record. Each row ends with a NEWLINE * <p>
* character. Each row contains one or more values. Values are separated by commas. A value can * Each row of text represents a row in a table or a data record. Each row
* contain any character except for comma, unless is is wrapped in single quotes or double quotes. * ends with a NEWLINE character. Each row contains one or more values.
* * Values are separated by commas. A value can contain any character except
* <p>The first row usually contains the names of the columns. * for comma, unless is is wrapped in single quotes or double quotes.
* * <p>
* <p>A comma delimited list can be converted into a JSONArray of JSONObjects. The names for the * The first row usually contains the names of the columns.
* elements in the JSONObjects can be taken from the names in the first row. * <p>
* A comma delimited list can be converted into a JSONArray of JSONObjects.
* The names for the elements in the JSONObjects can be taken from the names
* in the first row.
* *
* @author JSON.org * @author JSON.org
* @version 2010-12-24 * @version 2010-12-24
*/ */
public class CDL { public class CDL {
/** /**
* Get the next value. The value can be wrapped in quotes. The value can be empty. * Get the next value. The value can be wrapped in quotes. The value can
* * be empty.
* @param x A JSONTokener of the source text. *
* @return The value string, or null if empty. * @param x A JSONTokener of the source text.
* @throws JSONException if the quoted string is badly formed. * @return The value string, or null if empty.
*/ * @throws JSONException if the quoted string is badly formed.
private static String getValue(JSONTokener x) throws JSONException { */
char c; private static String getValue(JSONTokener x) throws JSONException {
char q; char c;
StringBuffer sb; char q;
do { StringBuffer sb;
c = x.next(); do {
} while (c == ' ' || c == '\t'); c = x.next();
switch (c) { } while (c == ' ' || c == '\t');
case 0: switch (c) {
return null; case 0:
case '"': return null;
case '\'': case '"':
q = c; case '\'':
sb = new StringBuffer(); q = c;
sb = new StringBuffer();
for (; ; ) {
c = x.next();
if (c == q) {
break;
}
if (c == 0 || c == '\n' || c == '\r') {
throw x.syntaxError("Missing close quote '" + q + "'.");
}
sb.append(c);
}
return sb.toString();
case ',':
x.back();
return "";
default:
x.back();
return x.nextTo(',');
}
}
/**
* Produce a JSONArray of strings from a row of comma delimited values.
*
* @param x A JSONTokener of the source text.
* @return A JSONArray of strings.
* @throws JSONException
*/
public static JSONArray rowToJSONArray(JSONTokener x) throws JSONException {
JSONArray ja = new JSONArray();
for (; ; ) { for (; ; ) {
c = x.next(); String value = getValue(x);
if (c == q) { char c = x.next();
break; if (value == null ||
} (ja.length() == 0 && value.length() == 0 && c != ',')) {
if (c == 0 || c == '\n' || c == '\r') { return null;
throw x.syntaxError("Missing close quote '" + q + "'."); }
} ja.put(value);
sb.append(c); for (; ; ) {
if (c == ',') {
break;
}
if (c != ' ') {
if (c == '\n' || c == '\r' || c == 0) {
return ja;
}
throw x.syntaxError("Bad character '" + c + "' (" +
(int) c + ").");
}
c = x.next();
}
}
}
/**
* Produce a JSONObject from a row of comma delimited text, using a
* parallel JSONArray of strings to provides the names of the elements.
*
* @param names A JSONArray of names. This is commonly obtained from the
* first row of a comma delimited text file using the rowToJSONArray
* method.
* @param x A JSONTokener of the source text.
* @return A JSONObject combining the names and values.
* @throws JSONException
*/
public static JSONObject rowToJSONObject(JSONArray names, JSONTokener x)
throws JSONException {
JSONArray ja = rowToJSONArray(x);
return ja != null ? ja.toJSONObject(names) : null;
}
/**
* Produce a comma delimited text row from a JSONArray. Values containing
* the comma character will be quoted. Troublesome characters may be
* removed.
*
* @param ja A JSONArray of strings.
* @return A string ending in NEWLINE.
*/
public static String rowToString(JSONArray ja) {
StringBuffer sb = new StringBuffer();
for (int i = 0; i < ja.length(); i += 1) {
if (i > 0) {
sb.append(',');
}
Object object = ja.opt(i);
if (object != null) {
String string = object.toString();
if (string.length() > 0 && (string.indexOf(',') >= 0 ||
string.indexOf('\n') >= 0 || string.indexOf('\r') >= 0 ||
string.indexOf(0) >= 0 || string.charAt(0) == '"')) {
sb.append('"');
int length = string.length();
for (int j = 0; j < length; j += 1) {
char c = string.charAt(j);
if (c >= ' ' && c != '"') {
sb.append(c);
}
}
sb.append('"');
} else {
sb.append(string);
}
}
}
sb.append('\n');
return sb.toString();
}
/**
* Produce a JSONArray of JSONObjects from a comma delimited text string,
* using the first row as a source of names.
*
* @param string The comma delimited text.
* @return A JSONArray of JSONObjects.
* @throws JSONException
*/
public static JSONArray toJSONArray(String string) throws JSONException {
return toJSONArray(new JSONTokener(string));
}
/**
* Produce a JSONArray of JSONObjects from a comma delimited text string,
* using the first row as a source of names.
*
* @param x The JSONTokener containing the comma delimited text.
* @return A JSONArray of JSONObjects.
* @throws JSONException
*/
public static JSONArray toJSONArray(JSONTokener x) throws JSONException {
return toJSONArray(rowToJSONArray(x), x);
}
/**
* Produce a JSONArray of JSONObjects from a comma delimited text string
* using a supplied JSONArray as the source of element names.
*
* @param names A JSONArray of strings.
* @param string The comma delimited text.
* @return A JSONArray of JSONObjects.
* @throws JSONException
*/
public static JSONArray toJSONArray(JSONArray names, String string)
throws JSONException {
return toJSONArray(names, new JSONTokener(string));
}
/**
* Produce a JSONArray of JSONObjects from a comma delimited text string
* using a supplied JSONArray as the source of element names.
*
* @param names A JSONArray of strings.
* @param x A JSONTokener of the source text.
* @return A JSONArray of JSONObjects.
* @throws JSONException
*/
public static JSONArray toJSONArray(JSONArray names, JSONTokener x)
throws JSONException {
if (names == null || names.length() == 0) {
return null;
}
JSONArray ja = new JSONArray();
for (; ; ) {
JSONObject jo = rowToJSONObject(names, x);
if (jo == null) {
break;
}
ja.put(jo);
}
if (ja.length() == 0) {
return null;
}
return ja;
}
/**
* Produce a comma delimited text from a JSONArray of JSONObjects. The
* first row will be a list of names obtained by inspecting the first
* JSONObject.
*
* @param ja A JSONArray of JSONObjects.
* @return A comma delimited text.
* @throws JSONException
*/
public static String toString(JSONArray ja) throws JSONException {
JSONObject jo = ja.optJSONObject(0);
if (jo != null) {
JSONArray names = jo.names();
if (names != null) {
return rowToString(names) + toString(names, ja);
}
}
return null;
}
/**
* Produce a comma delimited text from a JSONArray of JSONObjects using
* a provided list of names. The list of names is not included in the
* output.
*
* @param names A JSONArray of strings.
* @param ja A JSONArray of JSONObjects.
* @return A comma delimited text.
* @throws JSONException
*/
public static String toString(JSONArray names, JSONArray ja)
throws JSONException {
if (names == null || names.length() == 0) {
return null;
}
StringBuffer sb = new StringBuffer();
for (int i = 0; i < ja.length(); i += 1) {
JSONObject jo = ja.optJSONObject(i);
if (jo != null) {
sb.append(rowToString(jo.toJSONArray(names)));
}
} }
return sb.toString(); return sb.toString();
case ',':
x.back();
return "";
default:
x.back();
return x.nextTo(',');
} }
}
/**
* Produce a JSONArray of strings from a row of comma delimited values.
*
* @param x A JSONTokener of the source text.
* @return A JSONArray of strings.
* @throws JSONException
*/
public static JSONArray rowToJSONArray(JSONTokener x) throws JSONException {
JSONArray ja = new JSONArray();
for (; ; ) {
String value = getValue(x);
char c = x.next();
if (value == null || (ja.length() == 0 && value.isEmpty() && c != ',')) {
return null;
}
ja.put(value);
for (; ; ) {
if (c == ',') {
break;
}
if (c != ' ') {
if (c == '\n' || c == '\r' || c == 0) {
return ja;
}
throw x.syntaxError("Bad character '" + c + "' (" + (int) c + ").");
}
c = x.next();
}
}
}
/**
* Produce a JSONObject from a row of comma delimited text, using a parallel JSONArray of strings
* to provides the names of the elements.
*
* @param names A JSONArray of names. This is commonly obtained from the first row of a comma
* delimited text file using the rowToJSONArray method.
* @param x A JSONTokener of the source text.
* @return A JSONObject combining the names and values.
* @throws JSONException
*/
public static JSONObject rowToJSONObject(JSONArray names, JSONTokener x) throws JSONException {
JSONArray ja = rowToJSONArray(x);
return ja != null ? ja.toJSONObject(names) : null;
}
/**
* Produce a comma delimited text row from a JSONArray. Values containing the comma character will
* be quoted. Troublesome characters may be removed.
*
* @param ja A JSONArray of strings.
* @return A string ending in NEWLINE.
*/
public static String rowToString(JSONArray ja) {
StringBuffer sb = new StringBuffer();
for (int i = 0; i < ja.length(); i += 1) {
if (i > 0) {
sb.append(',');
}
Object object = ja.opt(i);
if (object != null) {
String string = object.toString();
if (string.length() > 0
&& (string.indexOf(',') >= 0
|| string.indexOf('\n') >= 0
|| string.indexOf('\r') >= 0
|| string.indexOf(0) >= 0
|| string.charAt(0) == '"')) {
sb.append('"');
int length = string.length();
for (int j = 0; j < length; j += 1) {
char c = string.charAt(j);
if (c >= ' ' && c != '"') {
sb.append(c);
}
}
sb.append('"');
} else {
sb.append(string);
}
}
}
sb.append('\n');
return sb.toString();
}
/**
* Produce a JSONArray of JSONObjects from a comma delimited text string, using the first row as a
* source of names.
*
* @param string The comma delimited text.
* @return A JSONArray of JSONObjects.
* @throws JSONException
*/
public static JSONArray toJSONArray(String string) throws JSONException {
return toJSONArray(new JSONTokener(string));
}
/**
* Produce a JSONArray of JSONObjects from a comma delimited text string, using the first row as a
* source of names.
*
* @param x The JSONTokener containing the comma delimited text.
* @return A JSONArray of JSONObjects.
* @throws JSONException
*/
public static JSONArray toJSONArray(JSONTokener x) throws JSONException {
return toJSONArray(rowToJSONArray(x), x);
}
/**
* Produce a JSONArray of JSONObjects from a comma delimited text string using a supplied
* JSONArray as the source of element names.
*
* @param names A JSONArray of strings.
* @param string The comma delimited text.
* @return A JSONArray of JSONObjects.
* @throws JSONException
*/
public static JSONArray toJSONArray(JSONArray names, String string) throws JSONException {
return toJSONArray(names, new JSONTokener(string));
}
/**
* Produce a JSONArray of JSONObjects from a comma delimited text string using a supplied
* JSONArray as the source of element names.
*
* @param names A JSONArray of strings.
* @param x A JSONTokener of the source text.
* @return A JSONArray of JSONObjects.
* @throws JSONException
*/
public static JSONArray toJSONArray(JSONArray names, JSONTokener x) throws JSONException {
if (names == null || names.length() == 0) {
return null;
}
JSONArray ja = new JSONArray();
for (; ; ) {
JSONObject jo = rowToJSONObject(names, x);
if (jo == null) {
break;
}
ja.put(jo);
}
if (ja.length() == 0) {
return null;
}
return ja;
}
/**
* Produce a comma delimited text from a JSONArray of JSONObjects. The first row will be a list of
* names obtained by inspecting the first JSONObject.
*
* @param ja A JSONArray of JSONObjects.
* @return A comma delimited text.
* @throws JSONException
*/
public static String toString(JSONArray ja) throws JSONException {
JSONObject jo = ja.optJSONObject(0);
if (jo != null) {
JSONArray names = jo.names();
if (names != null) {
return rowToString(names) + toString(names, ja);
}
}
return null;
}
/**
* Produce a comma delimited text from a JSONArray of JSONObjects using a provided list of names.
* The list of names is not included in the output.
*
* @param names A JSONArray of strings.
* @param ja A JSONArray of JSONObjects.
* @return A comma delimited text.
* @throws JSONException
*/
public static String toString(JSONArray names, JSONArray ja) throws JSONException {
if (names == null || names.length() == 0) {
return null;
}
StringBuffer sb = new StringBuffer();
for (int i = 0; i < ja.length(); i += 1) {
JSONObject jo = ja.optJSONObject(i);
if (jo != null) {
sb.append(rowToString(jo.toJSONArray(names)));
}
}
return sb.toString();
}
} }
@@ -17,139 +17,150 @@
package dev.brighten.antivpn.utils.json; package dev.brighten.antivpn.utils.json;
/** /**
* Convert a web browser cookie specification to a JSONObject and back. JSON and Cookies are both * Convert a web browser cookie specification to a JSONObject and back.
* notations for name/value pairs. * JSON and Cookies are both notations for name/value pairs.
* *
* @author JSON.org * @author JSON.org
* @version 2010-12-24 * @version 2010-12-24
*/ */
public class Cookie { public class Cookie {
/** /**
* Produce a copy of a string in which the characters '+', '%', '=', ';' and control characters * Produce a copy of a string in which the characters '+', '%', '=', ';'
* are replaced with "%hh". This is a gentle form of URL encoding, attempting to cause as little * and control characters are replaced with "%hh". This is a gentle form
* distortion to the string as possible. The characters '=' and ';' are meta characters in * of URL encoding, attempting to cause as little distortion to the
* cookies. By convention, they are escaped using the URL-encoding. This is only a convention, not * string as possible. The characters '=' and ';' are meta characters in
* a standard. Often, cookies are expected to have encoded values. We encode '=' and ';' because * cookies. By convention, they are escaped using the URL-encoding. This is
* we must. We encode '%' and '+' because they are meta characters in URL encoding. * only a convention, not a standard. Often, cookies are expected to have
* * encoded values. We encode '=' and ';' because we must. We encode '%' and
* @param string The source string. * '+' because they are meta characters in URL encoding.
* @return The escaped result. *
*/ * @param string The source string.
public static String escape(String string) { * @return The escaped result.
char c; */
String s = string.trim(); public static String escape(String string) {
StringBuffer sb = new StringBuffer(); char c;
int length = s.length(); String s = string.trim();
for (int i = 0; i < length; i += 1) { StringBuffer sb = new StringBuffer();
c = s.charAt(i); int length = s.length();
if (c < ' ' || c == '+' || c == '%' || c == '=' || c == ';') { for (int i = 0; i < length; i += 1) {
sb.append('%'); c = s.charAt(i);
sb.append(Character.forDigit((char) ((c >>> 4) & 0x0f), 16)); if (c < ' ' || c == '+' || c == '%' || c == '=' || c == ';') {
sb.append(Character.forDigit((char) (c & 0x0f), 16)); sb.append('%');
} else { sb.append(Character.forDigit((char) ((c >>> 4) & 0x0f), 16));
sb.append(c); sb.append(Character.forDigit((char) (c & 0x0f), 16));
} } else {
} sb.append(c);
return sb.toString(); }
}
/**
* Convert a cookie specification string into a JSONObject. The string will contain a name value
* pair separated by '='. The name and the value will be unescaped, possibly converting '+' and
* '%' sequences. The cookie properties may follow, separated by ';', also represented as
* name=value (except the secure property, which does not have a value). The name will be stored
* under the key "name", and the value will be stored under the key "value". This method does not
* do checking or validation of the parameters. It only converts the cookie string into a
* JSONObject.
*
* @param string The cookie specification string.
* @return A JSONObject containing "name", "value", and possibly other members.
* @throws JSONException
*/
public static JSONObject toJSONObject(String string) throws JSONException {
String name;
JSONObject jo = new JSONObject();
Object value;
JSONTokener x = new JSONTokener(string);
jo.put("name", x.nextTo('='));
x.next('=');
jo.put("value", x.nextTo(';'));
x.next();
while (x.more()) {
name = unescape(x.nextTo("=;"));
if (x.next() != '=') {
if (name.equals("secure")) {
value = Boolean.TRUE;
} else {
throw x.syntaxError("Missing '=' in cookie parameter.");
} }
} else { return sb.toString();
value = unescape(x.nextTo(';')); }
/**
* Convert a cookie specification string into a JSONObject. The string
* will contain a name value pair separated by '='. The name and the value
* will be unescaped, possibly converting '+' and '%' sequences. The
* cookie properties may follow, separated by ';', also represented as
* name=value (except the secure property, which does not have a value).
* The name will be stored under the key "name", and the value will be
* stored under the key "value". This method does not do checking or
* validation of the parameters. It only converts the cookie string into
* a JSONObject.
*
* @param string The cookie specification string.
* @return A JSONObject containing "name", "value", and possibly other
* members.
* @throws JSONException
*/
public static JSONObject toJSONObject(String string) throws JSONException {
String name;
JSONObject jo = new JSONObject();
Object value;
JSONTokener x = new JSONTokener(string);
jo.put("name", x.nextTo('='));
x.next('=');
jo.put("value", x.nextTo(';'));
x.next(); x.next();
} while (x.more()) {
jo.put(name, value); name = unescape(x.nextTo("=;"));
} if (x.next() != '=') {
return jo; if (name.equals("secure")) {
} value = Boolean.TRUE;
} else {
/** throw x.syntaxError("Missing '=' in cookie parameter.");
* Convert a JSONObject into a cookie specification string. The JSONObject must contain "name" and }
* "value" members. If the JSONObject contains "expires", "domain", "path", or "secure" members, } else {
* they will be appended to the cookie specification string. All other members are ignored. value = unescape(x.nextTo(';'));
* x.next();
* @param jo A JSONObject }
* @return A cookie specification string jo.put(name, value);
* @throws JSONException
*/
public static String toString(JSONObject jo) throws JSONException {
StringBuffer sb = new StringBuffer();
sb.append(escape(jo.getString("name")));
sb.append("=");
sb.append(escape(jo.getString("value")));
if (jo.has("expires")) {
sb.append(";expires=");
sb.append(jo.getString("expires"));
}
if (jo.has("domain")) {
sb.append(";domain=");
sb.append(escape(jo.getString("domain")));
}
if (jo.has("path")) {
sb.append(";path=");
sb.append(escape(jo.getString("path")));
}
if (jo.optBoolean("secure")) {
sb.append(";secure");
}
return sb.toString();
}
/**
* Convert <code>%</code><i>hh</i> sequences to single characters, and convert plus to space.
*
* @param string A string that may contain <code>+</code>&nbsp;<small>(plus)</small> and <code>%
* </code><i>hh</i> sequences.
* @return The unescaped string.
*/
public static String unescape(String string) {
int length = string.length();
StringBuffer sb = new StringBuffer();
for (int i = 0; i < length; ++i) {
char c = string.charAt(i);
if (c == '+') {
c = ' ';
} else if (c == '%' && i + 2 < length) {
int d = JSONTokener.dehexchar(string.charAt(i + 1));
int e = JSONTokener.dehexchar(string.charAt(i + 2));
if (d >= 0 && e >= 0) {
c = (char) (d * 16 + e);
i += 2;
} }
} return jo;
sb.append(c); }
/**
* Convert a JSONObject into a cookie specification string. The JSONObject
* must contain "name" and "value" members.
* If the JSONObject contains "expires", "domain", "path", or "secure"
* members, they will be appended to the cookie specification string.
* All other members are ignored.
*
* @param jo A JSONObject
* @return A cookie specification string
* @throws JSONException
*/
public static String toString(JSONObject jo) throws JSONException {
StringBuffer sb = new StringBuffer();
sb.append(escape(jo.getString("name")));
sb.append("=");
sb.append(escape(jo.getString("value")));
if (jo.has("expires")) {
sb.append(";expires=");
sb.append(jo.getString("expires"));
}
if (jo.has("domain")) {
sb.append(";domain=");
sb.append(escape(jo.getString("domain")));
}
if (jo.has("path")) {
sb.append(";path=");
sb.append(escape(jo.getString("path")));
}
if (jo.optBoolean("secure")) {
sb.append(";secure");
}
return sb.toString();
}
/**
* Convert <code>%</code><i>hh</i> sequences to single characters, and
* convert plus to space.
*
* @param string A string that may contain
* <code>+</code>&nbsp;<small>(plus)</small> and
* <code>%</code><i>hh</i> sequences.
* @return The unescaped string.
*/
public static String unescape(String string) {
int length = string.length();
StringBuffer sb = new StringBuffer();
for (int i = 0; i < length; ++i) {
char c = string.charAt(i);
if (c == '+') {
c = ' ';
} else if (c == '%' && i + 2 < length) {
int d = JSONTokener.dehexchar(string.charAt(i + 1));
int e = JSONTokener.dehexchar(string.charAt(i + 2));
if (d >= 0 && e >= 0) {
c = (char) (d * 16 + e);
i += 2;
}
}
sb.append(c);
}
return sb.toString();
} }
return sb.toString();
}
} }
@@ -26,56 +26,60 @@ import java.util.Iterator;
*/ */
public class CookieList { public class CookieList {
/** /**
* Convert a cookie list into a JSONObject. A cookie list is a sequence of name/value pairs. The * Convert a cookie list into a JSONObject. A cookie list is a sequence
* names are separated from the values by '='. The pairs are separated by ';'. The names and the * of name/value pairs. The names are separated from the values by '='.
* values will be unescaped, possibly converting '+' and '%' sequences. * The pairs are separated by ';'. The names and the values
* * will be unescaped, possibly converting '+' and '%' sequences.
* <p>To add a cookie to a cooklist, cookielistJSONObject.put(cookieJSONObject.getString("name"), * <p>
* cookieJSONObject.getString("value")); * To add a cookie to a cooklist,
* * cookielistJSONObject.put(cookieJSONObject.getString("name"),
* @param string A cookie list string * cookieJSONObject.getString("value"));
* @return A JSONObject *
* @throws JSONException * @param string A cookie list string
*/ * @return A JSONObject
public static JSONObject toJSONObject(String string) throws JSONException { * @throws JSONException
JSONObject jo = new JSONObject(); */
JSONTokener x = new JSONTokener(string); public static JSONObject toJSONObject(String string) throws JSONException {
while (x.more()) { JSONObject jo = new JSONObject();
String name = Cookie.unescape(x.nextTo('=')); JSONTokener x = new JSONTokener(string);
x.next('='); while (x.more()) {
jo.put(name, Cookie.unescape(x.nextTo(';'))); String name = Cookie.unescape(x.nextTo('='));
x.next(); x.next('=');
} jo.put(name, Cookie.unescape(x.nextTo(';')));
return jo; x.next();
}
/**
* Convert a JSONObject into a cookie list. A cookie list is a sequence of name/value pairs. The
* names are separated from the values by '='. The pairs are separated by ';'. The characters '%',
* '+', '=', and ';' in the names and values are replaced by "%hh".
*
* @param jo A JSONObject
* @return A cookie list string
* @throws JSONException
*/
public static String toString(JSONObject jo) throws JSONException {
boolean b = false;
Iterator keys = jo.keys();
String string;
StringBuffer sb = new StringBuffer();
while (keys.hasNext()) {
string = keys.next().toString();
if (!jo.isNull(string)) {
if (b) {
sb.append(';');
} }
sb.append(Cookie.escape(string)); return jo;
sb.append("="); }
sb.append(Cookie.escape(jo.getString(string)));
b = true;
} /**
* Convert a JSONObject into a cookie list. A cookie list is a sequence
* of name/value pairs. The names are separated from the values by '='.
* The pairs are separated by ';'. The characters '%', '+', '=', and ';'
* in the names and values are replaced by "%hh".
*
* @param jo A JSONObject
* @return A cookie list string
* @throws JSONException
*/
public static String toString(JSONObject jo) throws JSONException {
boolean b = false;
Iterator keys = jo.keys();
String string;
StringBuffer sb = new StringBuffer();
while (keys.hasNext()) {
string = keys.next().toString();
if (!jo.isNull(string)) {
if (b) {
sb.append(';');
}
sb.append(Cookie.escape(string));
sb.append("=");
sb.append(Cookie.escape(jo.getString(string)));
b = true;
}
}
return sb.toString();
} }
return sb.toString();
}
} }
@@ -26,146 +26,135 @@ import java.util.Iterator;
*/ */
public class HTTP { public class HTTP {
/** Carriage return/line feed. */ /**
public static final String CRLF = "\r\n"; * Carriage return/line feed.
*/
public static final String CRLF = "\r\n";
/** /**
* Convert an HTTP header string into a JSONObject. It can be a request header or a response * Convert an HTTP header string into a JSONObject. It can be a request
* header. A request header will contain * header or a response header. A request header will contain
* * <pre>{
* <pre>{ * Method: "POST" (for example),
* Method: "POST" (for example), * "Request-URI": "/" (for example),
* "Request-URI": "/" (for example), * "HTTP-Version": "HTTP/1.1" (for example)
* "HTTP-Version": "HTTP/1.1" (for example) * }</pre>
* }</pre> * A response header will contain
* * <pre>{
* A response header will contain * "HTTP-Version": "HTTP/1.1" (for example),
* * "Fixes-Code": "200" (for example),
* <pre>{ * "Reason-Phrase": "OK" (for example)
* "HTTP-Version": "HTTP/1.1" (for example), * }</pre>
* "Fixes-Code": "200" (for example), * In addition, the other parameters in the header will be captured, using
* "Reason-Phrase": "OK" (for example) * the HTTP field names as JSON names, so that <pre>
* }</pre> * Date: Sun, 26 May 2002 18:06:04 GMT
* * Cookie: Q=q2=PPEAsg--; B=677gi6ouf29bn&b=2&f=s
* In addition, the other parameters in the header will be captured, using the HTTP field names as * Cache-Control: no-cache</pre>
* JSON names, so that * become
* * <pre>{...
* <pre> * Date: "Sun, 26 May 2002 18:06:04 GMT",
* Date: Sun, 26 May 2002 18:06:04 GMT * Cookie: "Q=q2=PPEAsg--; B=677gi6ouf29bn&b=2&f=s",
* Cookie: Q=q2=PPEAsg--; B=677gi6ouf29bn&b=2&f=s * "Cache-Control": "no-cache",
* Cache-Control: no-cache</pre> * ...}</pre>
* * It does no further checking or conversion. It does not parse dates.
* become * It does not do '%' transforms on URLs.
* *
* <pre>{... * @param string An HTTP header string.
* Date: "Sun, 26 May 2002 18:06:04 GMT", * @return A JSONObject containing the elements and attributes
* Cookie: "Q=q2=PPEAsg--; B=677gi6ouf29bn&b=2&f=s", * of the XML string.
* "Cache-Control": "no-cache", * @throws JSONException
* ...}</pre> */
* public static JSONObject toJSONObject(String string) throws JSONException {
* It does no further checking or conversion. It does not parse dates. It does not do '%' JSONObject jo = new JSONObject();
* transforms on URLs. HTTPTokener x = new HTTPTokener(string);
* String token;
* @param string An HTTP header string.
* @return A JSONObject containing the elements and attributes of the XML string.
* @throws JSONException
*/
public static JSONObject toJSONObject(String string) throws JSONException {
JSONObject jo = new JSONObject();
HTTPTokener x = new HTTPTokener(string);
String token;
token = x.nextToken(); token = x.nextToken();
if (token.toUpperCase().startsWith("HTTP")) { if (token.toUpperCase().startsWith("HTTP")) {
// Response // Response
jo.put("HTTP-Version", token); jo.put("HTTP-Version", token);
jo.put("Fixes-Code", x.nextToken()); jo.put("Fixes-Code", x.nextToken());
jo.put("Reason-Phrase", x.nextTo('\0')); jo.put("Reason-Phrase", x.nextTo('\0'));
x.next(); x.next();
} else { } else {
// Request // Request
jo.put("Method", token); jo.put("Method", token);
jo.put("Request-URI", x.nextToken()); jo.put("Request-URI", x.nextToken());
jo.put("HTTP-Version", x.nextToken()); jo.put("HTTP-Version", x.nextToken());
}
// Fields
while (x.more()) {
String name = x.nextTo(':');
x.next(':');
jo.put(name, x.nextTo('\0'));
x.next();
}
return jo;
} }
// Fields
while (x.more()) { /**
String name = x.nextTo(':'); * Convert a JSONObject into an HTTP header. A request header must contain
x.next(':'); * <pre>{
jo.put(name, x.nextTo('\0')); * Method: "POST" (for example),
x.next(); * "Request-URI": "/" (for example),
} * "HTTP-Version": "HTTP/1.1" (for example)
return jo; * }</pre>
} * A response header must contain
* <pre>{
/** * "HTTP-Version": "HTTP/1.1" (for example),
* Convert a JSONObject into an HTTP header. A request header must contain * "Fixes-Code": "200" (for example),
* * "Reason-Phrase": "OK" (for example)
* <pre>{ * }</pre>
* Method: "POST" (for example), * Any other members of the JSONObject will be output as HTTP fields.
* "Request-URI": "/" (for example), * The result will end with two CRLF pairs.
* "HTTP-Version": "HTTP/1.1" (for example) *
* }</pre> * @param jo A JSONObject
* * @return An HTTP header string.
* A response header must contain * @throws JSONException if the object does not contain enough
* * information.
* <pre>{ */
* "HTTP-Version": "HTTP/1.1" (for example), public static String toString(JSONObject jo) throws JSONException {
* "Fixes-Code": "200" (for example), Iterator keys = jo.keys();
* "Reason-Phrase": "OK" (for example) String string;
* }</pre> StringBuffer sb = new StringBuffer();
* if (jo.has("Fixes-Code") && jo.has("Reason-Phrase")) {
* Any other members of the JSONObject will be output as HTTP fields. The result will end with two sb.append(jo.getString("HTTP-Version"));
* CRLF pairs. sb.append(' ');
* sb.append(jo.getString("Fixes-Code"));
* @param jo A JSONObject sb.append(' ');
* @return An HTTP header string. sb.append(jo.getString("Reason-Phrase"));
* @throws JSONException if the object does not contain enough information. } else if (jo.has("Method") && jo.has("Request-URI")) {
*/ sb.append(jo.getString("Method"));
public static String toString(JSONObject jo) throws JSONException { sb.append(' ');
Iterator keys = jo.keys(); sb.append('"');
String string; sb.append(jo.getString("Request-URI"));
StringBuffer sb = new StringBuffer(); sb.append('"');
if (jo.has("Fixes-Code") && jo.has("Reason-Phrase")) { sb.append(' ');
sb.append(jo.getString("HTTP-Version")); sb.append(jo.getString("HTTP-Version"));
sb.append(' '); } else {
sb.append(jo.getString("Fixes-Code")); throw new JSONException("Not enough material for an HTTP header.");
sb.append(' '); }
sb.append(jo.getString("Reason-Phrase"));
} else if (jo.has("Method") && jo.has("Request-URI")) {
sb.append(jo.getString("Method"));
sb.append(' ');
sb.append('"');
sb.append(jo.getString("Request-URI"));
sb.append('"');
sb.append(' ');
sb.append(jo.getString("HTTP-Version"));
} else {
throw new JSONException("Not enough material for an HTTP header.");
}
sb.append(CRLF);
while (keys.hasNext()) {
string = keys.next().toString();
if (!string.equals("HTTP-Version")
&& !string.equals("Fixes-Code")
&& !string.equals("Reason-Phrase")
&& !string.equals("Method")
&& !string.equals("Request-URI")
&& !jo.isNull(string)) {
sb.append(string);
sb.append(": ");
sb.append(jo.getString(string));
sb.append(CRLF); sb.append(CRLF);
} while (keys.hasNext()) {
string = keys.next().toString();
if (!string.equals("HTTP-Version") && !string.equals("Fixes-Code") &&
!string.equals("Reason-Phrase") && !string.equals("Method") &&
!string.equals("Request-URI") && !jo.isNull(string)) {
sb.append(string);
sb.append(": ");
sb.append(jo.getString(string));
sb.append(CRLF);
}
}
sb.append(CRLF);
return sb.toString();
} }
sb.append(CRLF);
return sb.toString();
}
} }
@@ -17,55 +17,56 @@
package dev.brighten.antivpn.utils.json; package dev.brighten.antivpn.utils.json;
/** /**
* The HTTPTokener extends the JSONTokener to provide additional methods for the parsing of HTTP * The HTTPTokener extends the JSONTokener to provide additional methods
* headers. * for the parsing of HTTP headers.
* *
* @author JSON.org * @author JSON.org
* @version 2010-12-24 * @version 2010-12-24
*/ */
public class HTTPTokener extends JSONTokener { public class HTTPTokener extends JSONTokener {
/** /**
* Construct an HTTPTokener from a string. * Construct an HTTPTokener from a string.
* *
* @param string A source string. * @param string A source string.
*/ */
public HTTPTokener(String string) { public HTTPTokener(String string) {
super(string); super(string);
} }
/**
* Get the next token or string. This is used in parsing HTTP headers. /**
* * Get the next token or string. This is used in parsing HTTP headers.
* @return A String. *
* @throws JSONException * @return A String.
*/ * @throws JSONException
public String nextToken() throws JSONException { */
char c; public String nextToken() throws JSONException {
char q; char c;
StringBuffer sb = new StringBuffer(); char q;
do { StringBuffer sb = new StringBuffer();
c = next(); do {
} while (Character.isWhitespace(c)); c = next();
if (c == '"' || c == '\'') { } while (Character.isWhitespace(c));
q = c; if (c == '"' || c == '\'') {
for (; ; ) { q = c;
c = next(); for (; ; ) {
if (c < ' ') { c = next();
throw syntaxError("Unterminated string."); if (c < ' ') {
throw syntaxError("Unterminated string.");
}
if (c == q) {
return sb.toString();
}
sb.append(c);
}
} }
if (c == q) { for (; ; ) {
return sb.toString(); if (c == 0 || Character.isWhitespace(c)) {
return sb.toString();
}
sb.append(c);
c = next();
} }
sb.append(c);
}
} }
for (; ; ) {
if (c == 0 || Character.isWhitespace(c)) {
return sb.toString();
}
sb.append(c);
c = next();
}
}
} }
File diff suppressed because it is too large Load Diff
@@ -23,24 +23,24 @@ package dev.brighten.antivpn.utils.json;
* @version 2010-12-24 * @version 2010-12-24
*/ */
public class JSONException extends Exception { public class JSONException extends Exception {
private static final long serialVersionUID = 0; private static final long serialVersionUID = 0;
private Throwable cause; private Throwable cause;
/** /**
* Constructs a JSONException with an explanatory message. * Constructs a JSONException with an explanatory message.
* *
* @param message Detail about the reason for the exception. * @param message Detail about the reason for the exception.
*/ */
public JSONException(String message) { public JSONException(String message) {
super(message); super(message);
} }
public JSONException(Throwable cause) { public JSONException(Throwable cause) {
super(cause.getMessage()); super(cause.getMessage());
this.cause = cause; this.cause = cause;
} }
public Throwable getCause() { public Throwable getCause() {
return this.cause; return this.cause;
} }
} }
@@ -18,420 +18,439 @@ package dev.brighten.antivpn.utils.json;
import java.util.Iterator; import java.util.Iterator;
/** /**
* This provides static methods to convert an XML text into a JSONArray or JSONObject, and to covert * This provides static methods to convert an XML text into a JSONArray or
* a JSONArray or JSONObject into an XML text using the JsonML transform. * JSONObject, and to covert a JSONArray or JSONObject into an XML text using
* the JsonML transform.
* *
* @author JSON.org * @author JSON.org
* @version 2010-12-23 * @version 2010-12-23
*/ */
public class JSONML { public class JSONML {
/** /**
* Parse XML values and store them in a JSONArray. * Parse XML values and store them in a JSONArray.
* *
* @param x The XMLTokener containing the source string. * @param x The XMLTokener containing the source string.
* @param arrayForm true if array form, false if object form. * @param arrayForm true if array form, false if object form.
* @param ja The JSONArray that is containing the current tag or null if we are at the outermost * @param ja The JSONArray that is containing the current tag or null
* level. * if we are at the outermost level.
* @return A JSONArray if the value is the outermost tag, otherwise null. * @return A JSONArray if the value is the outermost tag, otherwise null.
* @throws JSONException * @throws JSONException
*/ */
private static Object parse(XMLTokener x, boolean arrayForm, JSONArray ja) throws JSONException { private static Object parse(XMLTokener x, boolean arrayForm,
String attribute; JSONArray ja) throws JSONException {
char c; String attribute;
String closeTag = null; char c;
int i; String closeTag = null;
JSONArray newja = null; int i;
JSONObject newjo = null; JSONArray newja = null;
Object token; JSONObject newjo = null;
String tagName = null; Object token;
String tagName = null;
// Test for and skip past these forms: // Test for and skip past these forms:
// <!-- ... --> // <!-- ... -->
// <![ ... ]]> // <![ ... ]]>
// <! ... > // <! ... >
// <? ... ?> // <? ... ?>
while (true) { while (true) {
token = x.nextContent(); token = x.nextContent();
if (token == XML.LT) { if (token == XML.LT) {
token = x.nextToken(); token = x.nextToken();
if (token instanceof Character) { if (token instanceof Character) {
if (token == XML.SLASH) { if (token == XML.SLASH) {
// Close tag </ // Close tag </
token = x.nextToken(); token = x.nextToken();
if (!(token instanceof String)) { if (!(token instanceof String)) {
throw new JSONException("Expected a closing name instead of '" + token + "'."); throw new JSONException(
} "Expected a closing name instead of '" +
if (x.nextToken() != XML.GT) { token + "'.");
throw x.syntaxError("Misshaped close tag"); }
} if (x.nextToken() != XML.GT) {
return token; throw x.syntaxError("Misshaped close tag");
} else if (token == XML.BANG) { }
return token;
} else if (token == XML.BANG) {
// <! // <!
c = x.next(); c = x.next();
if (c == '-') { if (c == '-') {
if (x.next() == '-') { if (x.next() == '-') {
x.skipPast("-->"); x.skipPast("-->");
} }
x.back(); x.back();
} else if (c == '[') { } else if (c == '[') {
token = x.nextToken(); token = x.nextToken();
if (token.equals("CDATA") && x.next() == '[') { if (token.equals("CDATA") && x.next() == '[') {
if (ja != null) { if (ja != null) {
ja.put(x.nextCDATA()); ja.put(x.nextCDATA());
} }
} else { } else {
throw x.syntaxError("Expected 'CDATA['"); throw x.syntaxError("Expected 'CDATA['");
} }
} else { } else {
i = 1; i = 1;
do { do {
token = x.nextMeta(); token = x.nextMeta();
if (token == null) { if (token == null) {
throw x.syntaxError("Missing '>' after '<!'."); throw x.syntaxError("Missing '>' after '<!'.");
} else if (token == XML.LT) { } else if (token == XML.LT) {
i += 1; i += 1;
} else if (token == XML.GT) { } else if (token == XML.GT) {
i -= 1; i -= 1;
} }
} while (i > 0); } while (i > 0);
} }
} else if (token == XML.QUEST) { } else if (token == XML.QUEST) {
// <? // <?
x.skipPast("?>"); x.skipPast("?>");
} else { } else {
throw x.syntaxError("Misshaped tag"); throw x.syntaxError("Misshaped tag");
} }
// Open tag < // Open tag <
} else {
if (!(token instanceof String)) {
throw x.syntaxError("Bad tagName '" + token + "'.");
}
tagName = (String) token;
newja = new JSONArray();
newjo = new JSONObject();
if (arrayForm) {
newja.put(tagName);
if (ja != null) {
ja.put(newja);
}
} else {
newjo.put("tagName", tagName);
if (ja != null) {
ja.put(newjo);
}
}
token = null;
for (; ; ) {
if (token == null) {
token = x.nextToken();
}
if (token == null) {
throw x.syntaxError("Misshaped tag");
}
if (!(token instanceof String)) {
break;
}
// attribute = value
attribute = (String) token;
if (!arrayForm && (attribute == "tagName" || attribute == "childNode")) {
throw x.syntaxError("Reserved attribute.");
}
token = x.nextToken();
if (token == XML.EQ) {
token = x.nextToken();
if (!(token instanceof String)) {
throw x.syntaxError("Missing value");
}
newjo.accumulate(attribute, XML.stringToValue((String) token));
token = null;
} else {
newjo.accumulate(attribute, "");
}
}
if (arrayForm && newjo.length() > 0) {
newja.put(newjo);
}
// Empty tag <.../>
if (token == XML.SLASH) {
if (x.nextToken() != XML.GT) {
throw x.syntaxError("Misshaped tag");
}
if (ja == null) {
if (arrayForm) {
return newja;
} else {
return newjo;
}
}
// Content, between <...> and </...>
} else {
if (token != XML.GT) {
throw x.syntaxError("Misshaped tag");
}
closeTag = (String) parse(x, arrayForm, newja);
if (closeTag != null) {
if (!closeTag.equals(tagName)) {
throw x.syntaxError("Mismatched '" + tagName + "' and '" + closeTag + "'");
}
tagName = null;
if (!arrayForm && newja.length() > 0) {
newjo.put("childNodes", newja);
}
if (ja == null) {
if (arrayForm) {
return newja;
} else { } else {
return newjo; if (!(token instanceof String)) {
throw x.syntaxError("Bad tagName '" + token + "'.");
}
tagName = (String) token;
newja = new JSONArray();
newjo = new JSONObject();
if (arrayForm) {
newja.put(tagName);
if (ja != null) {
ja.put(newja);
}
} else {
newjo.put("tagName", tagName);
if (ja != null) {
ja.put(newjo);
}
}
token = null;
for (; ; ) {
if (token == null) {
token = x.nextToken();
}
if (token == null) {
throw x.syntaxError("Misshaped tag");
}
if (!(token instanceof String)) {
break;
}
// attribute = value
attribute = (String) token;
if (!arrayForm && (attribute == "tagName" || attribute == "childNode")) {
throw x.syntaxError("Reserved attribute.");
}
token = x.nextToken();
if (token == XML.EQ) {
token = x.nextToken();
if (!(token instanceof String)) {
throw x.syntaxError("Missing value");
}
newjo.accumulate(attribute, XML.stringToValue((String) token));
token = null;
} else {
newjo.accumulate(attribute, "");
}
}
if (arrayForm && newjo.length() > 0) {
newja.put(newjo);
}
// Empty tag <.../>
if (token == XML.SLASH) {
if (x.nextToken() != XML.GT) {
throw x.syntaxError("Misshaped tag");
}
if (ja == null) {
if (arrayForm) {
return newja;
} else {
return newjo;
}
}
// Content, between <...> and </...>
} else {
if (token != XML.GT) {
throw x.syntaxError("Misshaped tag");
}
closeTag = (String) parse(x, arrayForm, newja);
if (closeTag != null) {
if (!closeTag.equals(tagName)) {
throw x.syntaxError("Mismatched '" + tagName +
"' and '" + closeTag + "'");
}
tagName = null;
if (!arrayForm && newja.length() > 0) {
newjo.put("childNodes", newja);
}
if (ja == null) {
if (arrayForm) {
return newja;
} else {
return newjo;
}
}
}
}
}
} else {
if (ja != null) {
ja.put(token instanceof String ?
XML.stringToValue((String) token) : token);
} }
}
} }
}
} }
} else {
if (ja != null) {
ja.put(token instanceof String ? XML.stringToValue((String) token) : token);
}
}
}
}
/**
* Convert a well-formed (but not necessarily valid) XML string into a JSONArray using the JsonML
* transform. Each XML tag is represented as a JSONArray in which the first element is the tag
* name. If the tag has attributes, then the second element will be JSONObject containing the
* name/value pairs. If the tag contains children, then strings and JSONArrays will represent the
* child tags. Comments, prologs, DTDs, and <code>&lt;[ [ ]]></code> are ignored.
*
* @param string The source string.
* @return A JSONArray containing the structured data from the XML string.
* @throws JSONException
*/
public static JSONArray toJSONArray(String string) throws JSONException {
return toJSONArray(new XMLTokener(string));
}
/**
* Convert a well-formed (but not necessarily valid) XML string into a JSONArray using the JsonML
* transform. Each XML tag is represented as a JSONArray in which the first element is the tag
* name. If the tag has attributes, then the second element will be JSONObject containing the
* name/value pairs. If the tag contains children, then strings and JSONArrays will represent the
* child content and tags. Comments, prologs, DTDs, and <code>&lt;[ [ ]]></code> are ignored.
*
* @param x An XMLTokener.
* @return A JSONArray containing the structured data from the XML string.
* @throws JSONException
*/
public static JSONArray toJSONArray(XMLTokener x) throws JSONException {
return (JSONArray) parse(x, true, null);
}
/**
* Convert a well-formed (but not necessarily valid) XML string into a JSONObject using the JsonML
* transform. Each XML tag is represented as a JSONObject with a "tagName" property. If the tag
* has attributes, then the attributes will be in the JSONObject as properties. If the tag
* contains children, the object will have a "childNodes" property which will be an array of
* strings and JsonML JSONObjects.
*
* <p>Comments, prologs, DTDs, and <code>&lt;[ [ ]]></code> are ignored.
*
* @param x An XMLTokener of the XML source text.
* @return A JSONObject containing the structured data from the XML string.
* @throws JSONException
*/
public static JSONObject toJSONObject(XMLTokener x) throws JSONException {
return (JSONObject) parse(x, false, null);
}
/**
* Convert a well-formed (but not necessarily valid) XML string into a JSONObject using the JsonML
* transform. Each XML tag is represented as a JSONObject with a "tagName" property. If the tag
* has attributes, then the attributes will be in the JSONObject as properties. If the tag
* contains children, the object will have a "childNodes" property which will be an array of
* strings and JsonML JSONObjects.
*
* <p>Comments, prologs, DTDs, and <code>&lt;[ [ ]]></code> are ignored.
*
* @param string The XML source text.
* @return A JSONObject containing the structured data from the XML string.
* @throws JSONException
*/
public static JSONObject toJSONObject(String string) throws JSONException {
return toJSONObject(new XMLTokener(string));
}
/**
* Reverse the JSONML transformation, making an XML text from a JSONArray.
*
* @param ja A JSONArray.
* @return An XML string.
* @throws JSONException
*/
public static String toString(JSONArray ja) throws JSONException {
int i;
JSONObject jo;
String key;
Iterator keys;
int length;
Object object;
StringBuffer sb = new StringBuffer();
String tagName;
String value;
// Emit <tagName
tagName = ja.getString(0);
XML.noSpace(tagName);
tagName = XML.escape(tagName);
sb.append('<');
sb.append(tagName);
object = ja.opt(1);
if (object instanceof JSONObject) {
i = 2;
jo = (JSONObject) object;
// Emit the attributes
keys = jo.keys();
while (keys.hasNext()) {
key = keys.next().toString();
XML.noSpace(key);
value = jo.optString(key);
if (value != null) {
sb.append(' ');
sb.append(XML.escape(key));
sb.append('=');
sb.append('"');
sb.append(XML.escape(value));
sb.append('"');
}
}
} else {
i = 1;
} }
// Emit content in body
length = ja.length(); /**
if (i >= length) { * Convert a well-formed (but not necessarily valid) XML string into a
sb.append('/'); * JSONArray using the JsonML transform. Each XML tag is represented as
sb.append('>'); * a JSONArray in which the first element is the tag name. If the tag has
} else { * attributes, then the second element will be JSONObject containing the
sb.append('>'); * name/value pairs. If the tag contains children, then strings and
do { * JSONArrays will represent the child tags.
object = ja.get(i); * Comments, prologs, DTDs, and <code>&lt;[ [ ]]></code> are ignored.
i += 1; *
if (object != null) { * @param string The source string.
if (object instanceof String) { * @return A JSONArray containing the structured data from the XML string.
sb.append(XML.escape(object.toString())); * @throws JSONException
} else if (object instanceof JSONObject) { */
sb.append(toString((JSONObject) object)); public static JSONArray toJSONArray(String string) throws JSONException {
} else if (object instanceof JSONArray) { return toJSONArray(new XMLTokener(string));
sb.append(toString((JSONArray) object)); }
}
/**
* Convert a well-formed (but not necessarily valid) XML string into a
* JSONArray using the JsonML transform. Each XML tag is represented as
* a JSONArray in which the first element is the tag name. If the tag has
* attributes, then the second element will be JSONObject containing the
* name/value pairs. If the tag contains children, then strings and
* JSONArrays will represent the child content and tags.
* Comments, prologs, DTDs, and <code>&lt;[ [ ]]></code> are ignored.
*
* @param x An XMLTokener.
* @return A JSONArray containing the structured data from the XML string.
* @throws JSONException
*/
public static JSONArray toJSONArray(XMLTokener x) throws JSONException {
return (JSONArray) parse(x, true, null);
}
/**
* Convert a well-formed (but not necessarily valid) XML string into a
* JSONObject using the JsonML transform. Each XML tag is represented as
* a JSONObject with a "tagName" property. If the tag has attributes, then
* the attributes will be in the JSONObject as properties. If the tag
* contains children, the object will have a "childNodes" property which
* will be an array of strings and JsonML JSONObjects.
* <p>
* Comments, prologs, DTDs, and <code>&lt;[ [ ]]></code> are ignored.
*
* @param x An XMLTokener of the XML source text.
* @return A JSONObject containing the structured data from the XML string.
* @throws JSONException
*/
public static JSONObject toJSONObject(XMLTokener x) throws JSONException {
return (JSONObject) parse(x, false, null);
}
/**
* Convert a well-formed (but not necessarily valid) XML string into a
* JSONObject using the JsonML transform. Each XML tag is represented as
* a JSONObject with a "tagName" property. If the tag has attributes, then
* the attributes will be in the JSONObject as properties. If the tag
* contains children, the object will have a "childNodes" property which
* will be an array of strings and JsonML JSONObjects.
* <p>
* Comments, prologs, DTDs, and <code>&lt;[ [ ]]></code> are ignored.
*
* @param string The XML source text.
* @return A JSONObject containing the structured data from the XML string.
* @throws JSONException
*/
public static JSONObject toJSONObject(String string) throws JSONException {
return toJSONObject(new XMLTokener(string));
}
/**
* Reverse the JSONML transformation, making an XML text from a JSONArray.
*
* @param ja A JSONArray.
* @return An XML string.
* @throws JSONException
*/
public static String toString(JSONArray ja) throws JSONException {
int i;
JSONObject jo;
String key;
Iterator keys;
int length;
Object object;
StringBuffer sb = new StringBuffer();
String tagName;
String value;
// Emit <tagName
tagName = ja.getString(0);
XML.noSpace(tagName);
tagName = XML.escape(tagName);
sb.append('<');
sb.append(tagName);
object = ja.opt(1);
if (object instanceof JSONObject) {
i = 2;
jo = (JSONObject) object;
// Emit the attributes
keys = jo.keys();
while (keys.hasNext()) {
key = keys.next().toString();
XML.noSpace(key);
value = jo.optString(key);
if (value != null) {
sb.append(' ');
sb.append(XML.escape(key));
sb.append('=');
sb.append('"');
sb.append(XML.escape(value));
sb.append('"');
}
}
} else {
i = 1;
} }
} while (i < length);
sb.append('<');
sb.append('/');
sb.append(tagName);
sb.append('>');
}
return sb.toString();
}
/** //Emit content in body
* Reverse the JSONML transformation, making an XML text from a JSONObject. The JSONObject must
* contain a "tagName" property. If it has children, then it must have a "childNodes" property
* containing an array of objects. The other properties are attributes with string values.
*
* @param jo A JSONObject.
* @return An XML string.
* @throws JSONException
*/
public static String toString(JSONObject jo) throws JSONException {
StringBuffer sb = new StringBuffer();
int i;
JSONArray ja;
String key;
Iterator keys;
int length;
Object object;
String tagName;
String value;
// Emit <tagName length = ja.length();
if (i >= length) {
tagName = jo.optString("tagName"); sb.append('/');
if (tagName == null) { sb.append('>');
return XML.escape(jo.toString()); } else {
} sb.append('>');
XML.noSpace(tagName); do {
tagName = XML.escape(tagName); object = ja.get(i);
sb.append('<'); i += 1;
sb.append(tagName); if (object != null) {
if (object instanceof String) {
// Emit the attributes sb.append(XML.escape(object.toString()));
} else if (object instanceof JSONObject) {
keys = jo.keys(); sb.append(toString((JSONObject) object));
while (keys.hasNext()) { } else if (object instanceof JSONArray) {
key = keys.next().toString(); sb.append(toString((JSONArray) object));
if (!key.equals("tagName") && !key.equals("childNodes")) { }
XML.noSpace(key); }
value = jo.optString(key); } while (i < length);
if (value != null) { sb.append('<');
sb.append(' '); sb.append('/');
sb.append(XML.escape(key)); sb.append(tagName);
sb.append('='); sb.append('>');
sb.append('"');
sb.append(XML.escape(value));
sb.append('"');
} }
} return sb.toString();
} }
// Emit content in body /**
* Reverse the JSONML transformation, making an XML text from a JSONObject.
* The JSONObject must contain a "tagName" property. If it has children,
* then it must have a "childNodes" property containing an array of objects.
* The other properties are attributes with string values.
*
* @param jo A JSONObject.
* @return An XML string.
* @throws JSONException
*/
public static String toString(JSONObject jo) throws JSONException {
StringBuffer sb = new StringBuffer();
int i;
JSONArray ja;
String key;
Iterator keys;
int length;
Object object;
String tagName;
String value;
ja = jo.optJSONArray("childNodes"); //Emit <tagName
if (ja == null) {
sb.append('/'); tagName = jo.optString("tagName");
sb.append('>'); if (tagName == null) {
} else { return XML.escape(jo.toString());
sb.append('>');
length = ja.length();
for (i = 0; i < length; i += 1) {
object = ja.get(i);
if (object != null) {
if (object instanceof String) {
sb.append(XML.escape(object.toString()));
} else if (object instanceof JSONObject) {
sb.append(toString((JSONObject) object));
} else if (object instanceof JSONArray) {
sb.append(toString((JSONArray) object));
}
} }
} XML.noSpace(tagName);
sb.append('<'); tagName = XML.escape(tagName);
sb.append('/'); sb.append('<');
sb.append(tagName); sb.append(tagName);
sb.append('>');
//Emit the attributes
keys = jo.keys();
while (keys.hasNext()) {
key = keys.next().toString();
if (!key.equals("tagName") && !key.equals("childNodes")) {
XML.noSpace(key);
value = jo.optString(key);
if (value != null) {
sb.append(' ');
sb.append(XML.escape(key));
sb.append('=');
sb.append('"');
sb.append(XML.escape(value));
sb.append('"');
}
}
}
//Emit content in body
ja = jo.optJSONArray("childNodes");
if (ja == null) {
sb.append('/');
sb.append('>');
} else {
sb.append('>');
length = ja.length();
for (i = 0; i < length; i += 1) {
object = ja.get(i);
if (object != null) {
if (object instanceof String) {
sb.append(XML.escape(object.toString()));
} else if (object instanceof JSONObject) {
sb.append(toString((JSONObject) object));
} else if (object instanceof JSONArray) {
sb.append(toString((JSONArray) object));
}
}
}
sb.append('<');
sb.append('/');
sb.append(tagName);
sb.append('>');
}
return sb.toString();
} }
return sb.toString();
}
} }
File diff suppressed because it is too large Load Diff
@@ -17,17 +17,19 @@
package dev.brighten.antivpn.utils.json; package dev.brighten.antivpn.utils.json;
/** /**
* The <code>JSONString</code> interface allows a <code>toJSONString()</code> method so that a class * The <code>JSONString</code> interface allows a <code>toJSONString()</code>
* can change the behavior of <code>JSONObject.toString()</code>, <code>JSONArray.toString()</code>, * method so that a class can change the behavior of
* and <code>JSONWriter.value(</code>Object<code>)</code>. The <code>toJSONString</code> method will * <code>JSONObject.toString()</code>, <code>JSONArray.toString()</code>,
* be used instead of the default behavior of using the Object's <code>toString()</code> method and * and <code>JSONWriter.value(</code>Object<code>)</code>. The
* quoting the result. * <code>toJSONString</code> method will be used instead of the default behavior
* of using the Object's <code>toString()</code> method and quoting the result.
*/ */
public interface JSONString { public interface JSONString {
/** /**
* The <code>toJSONString</code> method allows a class to produce its own JSON serialization. * The <code>toJSONString</code> method allows a class to produce its own JSON
* * serialization.
* @return A strictly syntactically correct JSON text. *
*/ * @return A strictly syntactically correct JSON text.
String toJSONString(); */
public String toJSONString();
} }
@@ -19,53 +19,54 @@ package dev.brighten.antivpn.utils.json;
import java.io.StringWriter; import java.io.StringWriter;
/** /**
* JSONStringer provides a quick and convenient way of producing JSON text. The texts produced * JSONStringer provides a quick and convenient way of producing JSON text.
* strictly conform to JSON syntax rules. No whitespace is added, so the results are ready for * The texts produced strictly conform to JSON syntax rules. No whitespace is
* transmission or storage. Each instance of JSONStringer can produce one JSON text. * added, so the results are ready for transmission or storage. Each instance of
* * JSONStringer can produce one JSON text.
* <p>A JSONStringer instance provides a <code>value</code> method for appending values to the text, * <p>
* and a <code>key</code> method for adding keys before values in objects. There are <code>array * A JSONStringer instance provides a <code>value</code> method for appending
* </code> and <code>endArray</code> methods that make and bound array values, and <code>object * values to the
* </code> and <code>endObject</code> methods which make and bound object values. All of these * text, and a <code>key</code>
* methods return the JSONWriter instance, permitting cascade style. For example, * method for adding keys before values in objects. There are <code>array</code>
* * and <code>endArray</code> methods that make and bound array values, and
* <pre> * <code>object</code> and <code>endObject</code> methods which make and bound
* object values. All of these methods return the JSONWriter instance,
* permitting cascade style. For example, <pre>
* myString = new JSONStringer() * myString = new JSONStringer()
* .object() * .object()
* .key("JSON") * .key("JSON")
* .value("Hello, World!") * .value("Hello, World!")
* .endObject() * .endObject()
* .toString();</pre> * .toString();</pre> which produces the string <pre>
*
* which produces the string
*
* <pre>
* {"JSON":"Hello, World!"}</pre> * {"JSON":"Hello, World!"}</pre>
* * <p>
* <p>The first method called must be <code>array</code> or <code>object</code>. There are no * The first method called must be <code>array</code> or <code>object</code>.
* methods for adding commas or colons. JSONStringer adds them for you. Objects and arrays can be * There are no methods for adding commas or colons. JSONStringer adds them for
* nested up to 20 levels deep. * you. Objects and arrays can be nested up to 20 levels deep.
* * <p>
* <p>This can sometimes be easier than using a JSONObject to build a string. * This can sometimes be easier than using a JSONObject to build a string.
* *
* @author JSON.org * @author JSON.org
* @version 2008-09-18 * @version 2008-09-18
*/ */
public class JSONStringer extends JSONWriter { public class JSONStringer extends JSONWriter {
/** Make a fresh JSONStringer. It can be used to build one JSON text. */ /**
public JSONStringer() { * Make a fresh JSONStringer. It can be used to build one JSON text.
super(new StringWriter()); */
} public JSONStringer() {
super(new StringWriter());
}
/** /**
* Return the JSON text. This method is used to obtain the product of the JSONStringer instance. * Return the JSON text. This method is used to obtain the product of the
* It will return <code>null</code> if there was a problem in the construction of the JSON text * JSONStringer instance. It will return <code>null</code> if there was a
* (such as the calls to <code>array</code> were not properly balanced with calls to <code> * problem in the construction of the JSON text (such as the calls to
* endArray</code>). * <code>array</code> were not properly balanced with calls to
* * <code>endArray</code>).
* @return The JSON text. *
*/ * @return The JSON text.
public String toString() { */
return this.mode == 'd' ? this.writer.toString() : null; public String toString() {
} return this.mode == 'd' ? this.writer.toString() : null;
}
} }
@@ -19,391 +19,420 @@ package dev.brighten.antivpn.utils.json;
import java.io.*; import java.io.*;
/** /**
* A JSONTokener takes a source string and extracts characters and tokens from it. It is used by the * A JSONTokener takes a source string and extracts characters and tokens from
* JSONObject and JSONArray constructors to parse JSON source strings. * it. It is used by the JSONObject and JSONArray constructors to parse
* JSON source strings.
* *
* @author JSON.org * @author JSON.org
* @version 2010-12-24 * @version 2010-12-24
*/ */
public class JSONTokener { public class JSONTokener {
private int character; private int character;
private boolean eof; private boolean eof;
private int index; private int index;
private int line; private int line;
private char previous; private char previous;
private Reader reader; private Reader reader;
private boolean usePrevious; private boolean usePrevious;
/**
* Construct a JSONTokener from a Reader.
*
* @param reader A reader.
*/
public JSONTokener(Reader reader) {
this.reader = reader.markSupported() ? reader : new BufferedReader(reader);
this.eof = false;
this.usePrevious = false;
this.previous = 0;
this.index = 0;
this.character = 1;
this.line = 1;
}
/** Construct a JSONTokener from an InputStream. */ /**
public JSONTokener(InputStream inputStream) throws JSONException { * Construct a JSONTokener from a Reader.
this(new InputStreamReader(inputStream)); *
} * @param reader A reader.
*/
/** public JSONTokener(Reader reader) {
* Construct a JSONTokener from a string. this.reader = reader.markSupported() ?
* reader : new BufferedReader(reader);
* @param s A source string. this.eof = false;
*/ this.usePrevious = false;
public JSONTokener(String s) { this.previous = 0;
this(new StringReader(s)); this.index = 0;
} this.character = 1;
this.line = 1;
/**
* Get the hex value of a character (base16).
*
* @param c A character between '0' and '9' or between 'A' and 'F' or between 'a' and 'f'.
* @return An int between 0 and 15, or -1 if c was not a hex digit.
*/
public static int dehexchar(char c) {
if (c >= '0' && c <= '9') {
return c - '0';
}
if (c >= 'A' && c <= 'F') {
return c - ('A' - 10);
}
if (c >= 'a' && c <= 'f') {
return c - ('a' - 10);
}
return -1;
}
/**
* Back up one character. This provides a sort of lookahead capability, so that you can test for a
* digit or letter before attempting to parse the next number or identifier.
*/
public void back() throws JSONException {
if (usePrevious || index <= 0) {
throw new JSONException("Stepping back two steps is not supported");
}
this.index -= 1;
this.character -= 1;
this.usePrevious = true;
this.eof = false;
}
public boolean end() {
return eof && !usePrevious;
}
/**
* Determine if the source string still contains characters that next() can consume.
*
* @return true if not yet at the end of the source.
*/
public boolean more() throws JSONException {
next();
if (end()) {
return false;
}
back();
return true;
}
/**
* Get the next character in the source string.
*
* @return The next character, or 0 if past the end of the source string.
*/
public char next() throws JSONException {
int c;
if (this.usePrevious) {
this.usePrevious = false;
c = this.previous;
} else {
try {
c = this.reader.read();
} catch (IOException exception) {
throw new JSONException(exception);
}
if (c <= 0) { // End of stream
this.eof = true;
c = 0;
}
}
this.index += 1;
if (this.previous == '\r') {
this.line += 1;
this.character = c == '\n' ? 0 : 1;
} else if (c == '\n') {
this.line += 1;
this.character = 0;
} else {
this.character += 1;
}
this.previous = (char) c;
return this.previous;
}
/**
* Consume the next character, and check that it matches a specified character.
*
* @param c The character to match.
* @return The character.
* @throws JSONException if the character does not match.
*/
public char next(char c) throws JSONException {
char n = next();
if (n != c) {
throw syntaxError("Expected '" + c + "' and instead saw '" + n + "'");
}
return n;
}
/**
* Get the next n characters.
*
* @param n The number of characters to take.
* @return A string of n characters.
* @throws JSONException Substring bounds error if there are not n characters remaining in the
* source string.
*/
public String next(int n) throws JSONException {
if (n == 0) {
return "";
} }
char[] chars = new char[n];
int pos = 0;
while (pos < n) { /**
chars[pos] = next(); * Construct a JSONTokener from an InputStream.
if (end()) { */
throw syntaxError("Substring bounds error"); public JSONTokener(InputStream inputStream) throws JSONException {
} this(new InputStreamReader(inputStream));
pos += 1;
} }
return new String(chars);
}
/**
* Get the next char in the string, skipping whitespace. /**
* * Construct a JSONTokener from a string.
* @return A character, or 0 if there are no more characters. *
* @throws JSONException * @param s A source string.
*/ */
public char nextClean() throws JSONException { public JSONTokener(String s) {
for (; ; ) { this(new StringReader(s));
char c = next();
if (c == 0 || c > ' ') {
return c;
}
} }
}
/** /**
* Return the characters up to the next close quote character. Backslash processing is done. The * Get the hex value of a character (base16).
* formal JSON format does not allow strings in single quotes, but an implementation is allowed to *
* accept them. * @param c A character between '0' and '9' or between 'A' and 'F' or
* * between 'a' and 'f'.
* @param quote The quoting character, either <code>"</code>&nbsp;<small>(double quote)</small> or * @return An int between 0 and 15, or -1 if c was not a hex digit.
* <code>'</code>&nbsp;<small>(single quote)</small>. */
* @return A String. public static int dehexchar(char c) {
* @throws JSONException Unterminated string. if (c >= '0' && c <= '9') {
*/ return c - '0';
public String nextString(char quote) throws JSONException { }
char c; if (c >= 'A' && c <= 'F') {
StringBuffer sb = new StringBuffer(); return c - ('A' - 10);
for (; ; ) { }
c = next(); if (c >= 'a' && c <= 'f') {
switch (c) { return c - ('a' - 10);
case 0: }
case '\n': return -1;
case '\r': }
throw syntaxError("Unterminated string");
case '\\': /**
c = next(); * Back up one character. This provides a sort of lookahead capability,
switch (c) { * so that you can test for a digit or letter before attempting to parse
case 'b': * the next number or identifier.
sb.append('\b'); */
break; public void back() throws JSONException {
case 't': if (usePrevious || index <= 0) {
sb.append('\t'); throw new JSONException("Stepping back two steps is not supported");
break; }
case 'n': this.index -= 1;
sb.append('\n'); this.character -= 1;
break; this.usePrevious = true;
case 'f': this.eof = false;
sb.append('\f'); }
break;
case 'r': public boolean end() {
sb.append('\r'); return eof && !usePrevious;
break; }
case 'u':
sb.append((char) Integer.parseInt(next(4), 16));
break; /**
* Determine if the source string still contains characters that next()
* can consume.
*
* @return true if not yet at the end of the source.
*/
public boolean more() throws JSONException {
next();
if (end()) {
return false;
}
back();
return true;
}
/**
* Get the next character in the source string.
*
* @return The next character, or 0 if past the end of the source string.
*/
public char next() throws JSONException {
int c;
if (this.usePrevious) {
this.usePrevious = false;
c = this.previous;
} else {
try {
c = this.reader.read();
} catch (IOException exception) {
throw new JSONException(exception);
}
if (c <= 0) { // End of stream
this.eof = true;
c = 0;
}
}
this.index += 1;
if (this.previous == '\r') {
this.line += 1;
this.character = c == '\n' ? 0 : 1;
} else if (c == '\n') {
this.line += 1;
this.character = 0;
} else {
this.character += 1;
}
this.previous = (char) c;
return this.previous;
}
/**
* Consume the next character, and check that it matches a specified
* character.
*
* @param c The character to match.
* @return The character.
* @throws JSONException if the character does not match.
*/
public char next(char c) throws JSONException {
char n = next();
if (n != c) {
throw syntaxError("Expected '" + c + "' and instead saw '" +
n + "'");
}
return n;
}
/**
* Get the next n characters.
*
* @param n The number of characters to take.
* @return A string of n characters.
* @throws JSONException Substring bounds error if there are not
* n characters remaining in the source string.
*/
public String next(int n) throws JSONException {
if (n == 0) {
return "";
}
char[] chars = new char[n];
int pos = 0;
while (pos < n) {
chars[pos] = next();
if (end()) {
throw syntaxError("Substring bounds error");
}
pos += 1;
}
return new String(chars);
}
/**
* Get the next char in the string, skipping whitespace.
*
* @return A character, or 0 if there are no more characters.
* @throws JSONException
*/
public char nextClean() throws JSONException {
for (; ; ) {
char c = next();
if (c == 0 || c > ' ') {
return c;
}
}
}
/**
* Return the characters up to the next close quote character.
* Backslash processing is done. The formal JSON format does not
* allow strings in single quotes, but an implementation is allowed to
* accept them.
*
* @param quote The quoting character, either
* <code>"</code>&nbsp;<small>(double quote)</small> or
* <code>'</code>&nbsp;<small>(single quote)</small>.
* @return A String.
* @throws JSONException Unterminated string.
*/
public String nextString(char quote) throws JSONException {
char c;
StringBuffer sb = new StringBuffer();
for (; ; ) {
c = next();
switch (c) {
case 0:
case '\n':
case '\r':
throw syntaxError("Unterminated string");
case '\\':
c = next();
switch (c) {
case 'b':
sb.append('\b');
break;
case 't':
sb.append('\t');
break;
case 'n':
sb.append('\n');
break;
case 'f':
sb.append('\f');
break;
case 'r':
sb.append('\r');
break;
case 'u':
sb.append((char) Integer.parseInt(next(4), 16));
break;
case '"':
case '\'':
case '\\':
case '/':
sb.append(c);
break;
default:
throw syntaxError("Illegal escape.");
}
break;
default:
if (c == quote) {
return sb.toString();
}
sb.append(c);
}
}
}
/**
* Get the text up but not including the specified character or the
* end of line, whichever comes first.
*
* @param delimiter A delimiter character.
* @return A string.
*/
public String nextTo(char delimiter) throws JSONException {
StringBuffer sb = new StringBuffer();
for (; ; ) {
char c = next();
if (c == delimiter || c == 0 || c == '\n' || c == '\r') {
if (c != 0) {
back();
}
return sb.toString().trim();
}
sb.append(c);
}
}
/**
* Get the text up but not including one of the specified delimiter
* characters or the end of line, whichever comes first.
*
* @param delimiters A set of delimiter characters.
* @return A string, trimmed.
*/
public String nextTo(String delimiters) throws JSONException {
char c;
StringBuffer sb = new StringBuffer();
for (; ; ) {
c = next();
if (delimiters.indexOf(c) >= 0 || c == 0 ||
c == '\n' || c == '\r') {
if (c != 0) {
back();
}
return sb.toString().trim();
}
sb.append(c);
}
}
/**
* Get the next value. The value can be a Boolean, Double, Integer,
* JSONArray, JSONObject, Long, or String, or the JSONObject.NULL object.
*
* @return An object.
* @throws JSONException If syntax error.
*/
public Object nextValue() throws JSONException {
char c = nextClean();
String string;
switch (c) {
case '"': case '"':
case '\'': case '\'':
case '\\': return nextString(c);
case '/': case '{':
sb.append(c); back();
break; return new JSONObject(this);
default: case '[':
throw syntaxError("Illegal escape."); back();
} return new JSONArray(this);
break;
default:
if (c == quote) {
return sb.toString();
}
sb.append(c);
}
}
}
/**
* Get the text up but not including the specified character or the end of line, whichever comes
* first.
*
* @param delimiter A delimiter character.
* @return A string.
*/
public String nextTo(char delimiter) throws JSONException {
StringBuffer sb = new StringBuffer();
for (; ; ) {
char c = next();
if (c == delimiter || c == 0 || c == '\n' || c == '\r') {
if (c != 0) {
back();
} }
return sb.toString().trim();
}
sb.append(c);
}
}
/** /*
* Get the text up but not including one of the specified delimiter characters or the end of line, * Handle unquoted text. This could be the values true, false, or
* whichever comes first. * null, or it can be a number. An implementation (such as this one)
* * is allowed to also accept non-standard forms.
* @param delimiters A set of delimiter characters. *
* @return A string, trimmed. * Accumulate characters until we reach the end of the text or a
*/ * formatting character.
public String nextTo(String delimiters) throws JSONException { */
char c;
StringBuffer sb = new StringBuffer(); StringBuffer sb = new StringBuffer();
for (; ; ) { while (c >= ' ' && ",:]}/\\\"[{;=#".indexOf(c) < 0) {
c = next(); sb.append(c);
if (delimiters.indexOf(c) >= 0 || c == 0 || c == '\n' || c == '\r') { c = next();
if (c != 0) {
back();
} }
return sb.toString().trim();
}
sb.append(c);
}
}
/**
* Get the next value. The value can be a Boolean, Double, Integer, JSONArray, JSONObject, Long,
* or String, or the JSONObject.NULL object.
*
* @return An object.
* @throws JSONException If syntax error.
*/
public Object nextValue() throws JSONException {
char c = nextClean();
String string;
switch (c) {
case '"':
case '\'':
return nextString(c);
case '{':
back(); back();
return new JSONObject(this);
case '[': string = sb.toString().trim();
back(); if (string.equals("")) {
return new JSONArray(this); throw syntaxError("Missing value");
}
return JSONObject.stringToValue(string);
} }
/*
* Handle unquoted text. This could be the values true, false, or /**
* null, or it can be a number. An implementation (such as this one) * Skip characters until the next character is the requested character.
* is allowed to also accept non-standard forms. * If the requested character is not found, no characters are skipped.
* *
* Accumulate characters until we reach the end of the text or a * @param to A character to skip to.
* formatting character. * @return The requested character, or zero if the requested character
* is not found.
*/ */
public char skipTo(char to) throws JSONException {
StringBuffer sb = new StringBuffer(); char c;
while (c >= ' ' && ",:]}/\\\"[{;=#".indexOf(c) < 0) { try {
sb.append(c); int startIndex = this.index;
c = next(); int startCharacter = this.character;
} int startLine = this.line;
back(); reader.mark(Integer.MAX_VALUE);
do {
string = sb.toString().trim(); c = next();
if (string.isEmpty()) { if (c == 0) {
throw syntaxError("Missing value"); reader.reset();
} this.index = startIndex;
return JSONObject.stringToValue(string); this.character = startCharacter;
} this.line = startLine;
return c;
/** }
* Skip characters until the next character is the requested character. If the requested character } while (c != to);
* is not found, no characters are skipped. } catch (IOException exc) {
* throw new JSONException(exc);
* @param to A character to skip to.
* @return The requested character, or zero if the requested character is not found.
*/
public char skipTo(char to) throws JSONException {
char c;
try {
int startIndex = this.index;
int startCharacter = this.character;
int startLine = this.line;
reader.mark(Integer.MAX_VALUE);
do {
c = next();
if (c == 0) {
reader.reset();
this.index = startIndex;
this.character = startCharacter;
this.line = startLine;
return c;
} }
} while (c != to);
} catch (IOException exc) { back();
throw new JSONException(exc); return c;
} }
back();
return c;
}
/** /**
* Make a JSONException to signal a syntax error. * Make a JSONException to signal a syntax error.
* *
* @param message The error message. * @param message The error message.
* @return A JSONException object, suitable for throwing * @return A JSONException object, suitable for throwing
*/ */
public JSONException syntaxError(String message) { public JSONException syntaxError(String message) {
return new JSONException(message + toString()); return new JSONException(message + toString());
} }
/**
* Make a printable string of this JSONTokener. /**
* * Make a printable string of this JSONTokener.
* @return " at {index} [character {character} line {line}]" *
*/ * @return " at {index} [character {character} line {line}]"
public String toString() { */
return " at " + index + " [character " + this.character + " line " + this.line + "]"; public String toString() {
} return " at " + index + " [character " + this.character + " line " +
this.line + "]";
}
} }
@@ -20,282 +20,305 @@ import java.io.IOException;
import java.io.Writer; import java.io.Writer;
/** /**
* JSONWriter provides a quick and convenient way of producing JSON text. The texts produced * JSONWriter provides a quick and convenient way of producing JSON text.
* strictly conform to JSON syntax rules. No whitespace is added, so the results are ready for * The texts produced strictly conform to JSON syntax rules. No whitespace is
* transmission or storage. Each instance of JSONWriter can produce one JSON text. * added, so the results are ready for transmission or storage. Each instance of
* * JSONWriter can produce one JSON text.
* <p>A JSONWriter instance provides a <code>value</code> method for appending values to the text, * <p>
* and a <code>key</code> method for adding keys before values in objects. There are <code>array * A JSONWriter instance provides a <code>value</code> method for appending
* </code> and <code>endArray</code> methods that make and bound array values, and <code>object * values to the
* </code> and <code>endObject</code> methods which make and bound object values. All of these * text, and a <code>key</code>
* methods return the JSONWriter instance, permitting a cascade style. For example, * method for adding keys before values in objects. There are <code>array</code>
* * and <code>endArray</code> methods that make and bound array values, and
* <pre> * <code>object</code> and <code>endObject</code> methods which make and bound
* object values. All of these methods return the JSONWriter instance,
* permitting a cascade style. For example, <pre>
* new JSONWriter(myWriter) * new JSONWriter(myWriter)
* .object() * .object()
* .key("JSON") * .key("JSON")
* .value("Hello, World!") * .value("Hello, World!")
* .endObject();</pre> * .endObject();</pre> which writes <pre>
*
* which writes
*
* <pre>
* {"JSON":"Hello, World!"}</pre> * {"JSON":"Hello, World!"}</pre>
* * <p>
* <p>The first method called must be <code>array</code> or <code>object</code>. There are no * The first method called must be <code>array</code> or <code>object</code>.
* methods for adding commas or colons. JSONWriter adds them for you. Objects and arrays can be * There are no methods for adding commas or colons. JSONWriter adds them for
* nested up to 20 levels deep. * you. Objects and arrays can be nested up to 20 levels deep.
* * <p>
* <p>This can sometimes be easier than using a JSONObject to build a string. * This can sometimes be easier than using a JSONObject to build a string.
* *
* @author JSON.org * @author JSON.org
* @version 2010-12-24 * @version 2010-12-24
*/ */
public class JSONWriter { public class JSONWriter {
private static final int maxdepth = 20; private static final int maxdepth = 20;
/**
* The current mode. Values:
* 'a' (array),
* 'd' (done),
* 'i' (initial),
* 'k' (key),
* 'o' (object).
*/
protected char mode;
/**
* The writer that will receive the output.
*/
protected Writer writer;
/**
* The comma flag determines if a comma should be output before the next
* value.
*/
private boolean comma;
/**
* The object/array stack.
*/
private JSONObject stack[];
/**
* The stack top index. A value of 0 indicates that the stack is empty.
*/
private int top;
/** The current mode. Values: 'a' (array), 'd' (done), 'i' (initial), 'k' (key), 'o' (object). */ /**
protected char mode; * Make a fresh JSONWriter. It can be used to build one JSON text.
*/
/** The writer that will receive the output. */ public JSONWriter(Writer w) {
protected Writer writer;
/** The comma flag determines if a comma should be output before the next value. */
private boolean comma;
/** The object/array stack. */
private JSONObject stack[];
/** The stack top index. A value of 0 indicates that the stack is empty. */
private int top;
/** Make a fresh JSONWriter. It can be used to build one JSON text. */
public JSONWriter(Writer w) {
this.comma = false;
this.mode = 'i';
this.stack = new JSONObject[maxdepth];
this.top = 0;
this.writer = w;
}
/**
* Append a value.
*
* @param string A string value.
* @return this
* @throws JSONException If the value is out of sequence.
*/
private JSONWriter append(String string) throws JSONException {
if (string == null) {
throw new JSONException("Null pointer");
}
if (this.mode == 'o' || this.mode == 'a') {
try {
if (this.comma && this.mode == 'a') {
this.writer.write(',');
}
this.writer.write(string);
} catch (IOException e) {
throw new JSONException(e);
}
if (this.mode == 'o') {
this.mode = 'k';
}
this.comma = true;
return this;
}
throw new JSONException("Value out of sequence.");
}
/**
* Begin appending a new array. All values until the balancing <code>endArray</code> will be
* appended to this array. The <code>endArray</code> method must be called to mark the array's
* end.
*
* @return this
* @throws JSONException If the nesting is too deep, or if the object is started in the wrong
* place (for example as a key or after the end of the outermost array or object).
*/
public JSONWriter array() throws JSONException {
if (this.mode == 'i' || this.mode == 'o' || this.mode == 'a') {
this.push(null);
this.append("[");
this.comma = false;
return this;
}
throw new JSONException("Misplaced array.");
}
/**
* End something.
*
* @param mode Mode
* @param c Closing character
* @return this
* @throws JSONException If unbalanced.
*/
private JSONWriter end(char mode, char c) throws JSONException {
if (this.mode != mode) {
throw new JSONException(mode == 'a' ? "Misplaced endArray." : "Misplaced endObject.");
}
this.pop(mode);
try {
this.writer.write(c);
} catch (IOException e) {
throw new JSONException(e);
}
this.comma = true;
return this;
}
/**
* End an array. This method most be called to balance calls to <code>array</code>.
*
* @return this
* @throws JSONException If incorrectly nested.
*/
public JSONWriter endArray() throws JSONException {
return this.end('a', ']');
}
/**
* End an object. This method most be called to balance calls to <code>object</code>.
*
* @return this
* @throws JSONException If incorrectly nested.
*/
public JSONWriter endObject() throws JSONException {
return this.end('k', '}');
}
/**
* Append a key. The key will be associated with the next value. In an object, every value must be
* preceded by a key.
*
* @param string A key string.
* @return this
* @throws JSONException If the key is out of place. For example, keys do not belong in arrays or
* if the key is null.
*/
public JSONWriter key(String string) throws JSONException {
if (string == null) {
throw new JSONException("Null key.");
}
if (this.mode == 'k') {
try {
stack[top - 1].putOnce(string, Boolean.TRUE);
if (this.comma) {
this.writer.write(',');
}
this.writer.write(JSONObject.quote(string));
this.writer.write(':');
this.comma = false; this.comma = false;
this.mode = 'o'; this.mode = 'i';
this.stack = new JSONObject[maxdepth];
this.top = 0;
this.writer = w;
}
/**
* Append a value.
*
* @param string A string value.
* @return this
* @throws JSONException If the value is out of sequence.
*/
private JSONWriter append(String string) throws JSONException {
if (string == null) {
throw new JSONException("Null pointer");
}
if (this.mode == 'o' || this.mode == 'a') {
try {
if (this.comma && this.mode == 'a') {
this.writer.write(',');
}
this.writer.write(string);
} catch (IOException e) {
throw new JSONException(e);
}
if (this.mode == 'o') {
this.mode = 'k';
}
this.comma = true;
return this;
}
throw new JSONException("Value out of sequence.");
}
/**
* Begin appending a new array. All values until the balancing
* <code>endArray</code> will be appended to this array. The
* <code>endArray</code> method must be called to mark the array's end.
*
* @return this
* @throws JSONException If the nesting is too deep, or if the object is
* started in the wrong place (for example as a key or after the end of the
* outermost array or object).
*/
public JSONWriter array() throws JSONException {
if (this.mode == 'i' || this.mode == 'o' || this.mode == 'a') {
this.push(null);
this.append("[");
this.comma = false;
return this;
}
throw new JSONException("Misplaced array.");
}
/**
* End something.
*
* @param mode Mode
* @param c Closing character
* @return this
* @throws JSONException If unbalanced.
*/
private JSONWriter end(char mode, char c) throws JSONException {
if (this.mode != mode) {
throw new JSONException(mode == 'a' ? "Misplaced endArray." :
"Misplaced endObject.");
}
this.pop(mode);
try {
this.writer.write(c);
} catch (IOException e) {
throw new JSONException(e);
}
this.comma = true;
return this; return this;
} catch (IOException e) {
throw new JSONException(e);
}
} }
throw new JSONException("Misplaced key.");
}
/** /**
* Begin appending a new object. All keys and values until the balancing <code>endObject</code> * End an array. This method most be called to balance calls to
* will be appended to this object. The <code>endObject</code> method must be called to mark the * <code>array</code>.
* object's end. *
* * @return this
* @return this * @throws JSONException If incorrectly nested.
* @throws JSONException If the nesting is too deep, or if the object is started in the wrong */
* place (for example as a key or after the end of the outermost array or object). public JSONWriter endArray() throws JSONException {
*/ return this.end('a', ']');
public JSONWriter object() throws JSONException {
if (this.mode == 'i') {
this.mode = 'o';
} }
if (this.mode == 'o' || this.mode == 'a') {
this.append("{"); /**
this.push(new JSONObject()); * End an object. This method most be called to balance calls to
this.comma = false; * <code>object</code>.
return this; *
* @return this
* @throws JSONException If incorrectly nested.
*/
public JSONWriter endObject() throws JSONException {
return this.end('k', '}');
} }
throw new JSONException("Misplaced object.");
}
/** /**
* Pop an array or object scope. * Append a key. The key will be associated with the next value. In an
* * object, every value must be preceded by a key.
* @param c The scope to close. *
* @throws JSONException If nesting is wrong. * @param string A key string.
*/ * @return this
private void pop(char c) throws JSONException { * @throws JSONException If the key is out of place. For example, keys
if (this.top <= 0) { * do not belong in arrays or if the key is null.
throw new JSONException("Nesting error."); */
public JSONWriter key(String string) throws JSONException {
if (string == null) {
throw new JSONException("Null key.");
}
if (this.mode == 'k') {
try {
stack[top - 1].putOnce(string, Boolean.TRUE);
if (this.comma) {
this.writer.write(',');
}
this.writer.write(JSONObject.quote(string));
this.writer.write(':');
this.comma = false;
this.mode = 'o';
return this;
} catch (IOException e) {
throw new JSONException(e);
}
}
throw new JSONException("Misplaced key.");
} }
char m = this.stack[this.top - 1] == null ? 'a' : 'k';
if (m != c) {
throw new JSONException("Nesting error."); /**
* Begin appending a new object. All keys and values until the balancing
* <code>endObject</code> will be appended to this object. The
* <code>endObject</code> method must be called to mark the object's end.
*
* @return this
* @throws JSONException If the nesting is too deep, or if the object is
* started in the wrong place (for example as a key or after the end of the
* outermost array or object).
*/
public JSONWriter object() throws JSONException {
if (this.mode == 'i') {
this.mode = 'o';
}
if (this.mode == 'o' || this.mode == 'a') {
this.append("{");
this.push(new JSONObject());
this.comma = false;
return this;
}
throw new JSONException("Misplaced object.");
} }
this.top -= 1;
this.mode = this.top == 0 ? 'd' : this.stack[this.top - 1] == null ? 'a' : 'k';
}
/**
* Push an array or object scope. /**
* * Pop an array or object scope.
* @param c The scope to open. *
* @throws JSONException If nesting is too deep. * @param c The scope to close.
*/ * @throws JSONException If nesting is wrong.
private void push(JSONObject jo) throws JSONException { */
if (this.top >= maxdepth) { private void pop(char c) throws JSONException {
throw new JSONException("Nesting too deep."); if (this.top <= 0) {
throw new JSONException("Nesting error.");
}
char m = this.stack[this.top - 1] == null ? 'a' : 'k';
if (m != c) {
throw new JSONException("Nesting error.");
}
this.top -= 1;
this.mode = this.top == 0 ?
'd' : this.stack[this.top - 1] == null ? 'a' : 'k';
} }
this.stack[this.top] = jo;
this.mode = jo == null ? 'a' : 'k';
this.top += 1;
}
/** /**
* Append either the value <code>true</code> or the value <code>false</code>. * Push an array or object scope.
* *
* @param b A boolean. * @param c The scope to open.
* @return this * @throws JSONException If nesting is too deep.
* @throws JSONException */
*/ private void push(JSONObject jo) throws JSONException {
public JSONWriter value(boolean b) throws JSONException { if (this.top >= maxdepth) {
return this.append(b ? "true" : "false"); throw new JSONException("Nesting too deep.");
} }
this.stack[this.top] = jo;
this.mode = jo == null ? 'a' : 'k';
this.top += 1;
}
/**
* Append a double value.
*
* @param d A double.
* @return this
* @throws JSONException If the number is not finite.
*/
public JSONWriter value(double d) throws JSONException {
return this.value(new Double(d));
}
/** /**
* Append a long value. * Append either the value <code>true</code> or the value
* * <code>false</code>.
* @param l A long. *
* @return this * @param b A boolean.
* @throws JSONException * @return this
*/ * @throws JSONException
public JSONWriter value(long l) throws JSONException { */
return this.append(Long.toString(l)); public JSONWriter value(boolean b) throws JSONException {
} return this.append(b ? "true" : "false");
}
/** /**
* Append an object value. * Append a double value.
* *
* @param object The object to append. It can be null, or a Boolean, Number, String, JSONObject, * @param d A double.
* or JSONArray, or an object that implements JSONString. * @return this
* @return this * @throws JSONException If the number is not finite.
* @throws JSONException If the value is out of sequence. */
*/ public JSONWriter value(double d) throws JSONException {
public JSONWriter value(Object object) throws JSONException { return this.value(new Double(d));
return this.append(JSONObject.valueToString(object)); }
}
/**
* Append a long value.
*
* @param l A long.
* @return this
* @throws JSONException
*/
public JSONWriter value(long l) throws JSONException {
return this.append(Long.toString(l));
}
/**
* Append an object value.
*
* @param object The object to append. It can be null, or a Boolean, Number,
* String, JSONObject, or JSONArray, or an object that implements JSONString.
* @return this
* @throws JSONException If the value is out of sequence.
*/
public JSONWriter value(Object object) throws JSONException {
return this.append(JSONObject.valueToString(object));
}
} }
@@ -15,31 +15,30 @@
*/ */
package dev.brighten.antivpn.utils.json; package dev.brighten.antivpn.utils.json;
import java.io.*; import java.io.*;
import java.net.URL; import java.net.URL;
import java.nio.charset.Charset; import java.nio.charset.Charset;
public class JsonReader { public class JsonReader {
public static String readAll(Reader rd) throws IOException { public static String readAll(Reader rd) throws IOException {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
int cp; int cp;
while ((cp = rd.read()) != -1) { while ((cp = rd.read()) != -1) {
sb.append((char) cp); sb.append((char) cp);
}
return sb.toString();
} }
return sb.toString();
}
public static JSONObject readJsonFromUrl(String url) throws IOException, JSONException { public static JSONObject readJsonFromUrl(String url) throws IOException, JSONException {
InputStream is = new URL(url).openStream(); InputStream is = new URL(url).openStream();
try { try {
BufferedReader rd = new BufferedReader(new InputStreamReader(is, Charset.forName("UTF-8"))); BufferedReader rd = new BufferedReader(new InputStreamReader(is, Charset.forName("UTF-8")));
String jsonText = readAll(rd); String jsonText = readAll(rd);
JSONObject json = new JSONObject(jsonText); JSONObject json = new JSONObject(jsonText);
return json; return json;
} finally { } finally {
is.close(); is.close();
}
} }
}
} }

Some files were not shown because too many files have changed in this diff Show More