Merge pull request #92 from funkemunky/bugfix/spam-kick

Fixing player spam kick
This commit is contained in:
Dawson
2026-05-03 14:30:47 -04:00
committed by GitHub
137 changed files with 14082 additions and 12223 deletions
+1288
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -31,4 +31,4 @@ jobs:
name: AntiVPN-Universal name: AntiVPN-Universal
path: build/libs/AntiVPN-*-universal.jar path: build/libs/AntiVPN-*-universal.jar
- name: Test - name: Test
run: gradle test --no-daemon run: gradle test --no-daemon --parallel
+4
View File
@@ -30,4 +30,8 @@ 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
@@ -29,7 +29,8 @@ public class BukkitLoaderPlugin extends JavaPlugin {
private final LoaderBootstrap plugin; private final LoaderBootstrap plugin;
public BukkitLoaderPlugin() { public BukkitLoaderPlugin() {
JarInJarClassLoader loader = new JarInJarClassLoader(getClass().getClassLoader(), JAR_NAME, SOURCE_NAME); JarInJarClassLoader loader =
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);
} }
@@ -47,6 +48,4 @@ public class BukkitLoaderPlugin extends JavaPlugin {
public void onDisable() { public void onDisable() {
this.plugin.onDisable(); this.plugin.onDisable();
} }
} }
+1
View File
@@ -12,6 +12,7 @@ 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,13 +19,12 @@ 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 {
@@ -33,8 +32,8 @@ public class BukkitCommandExecutor implements CommandExecutor {
@Override @Override
public void sendMessage(String message, Object... objects) { public void sendMessage(String message, Object... objects) {
sender.sendMessage(ChatColor.translateAlternateColorCodes('&', sender.sendMessage(
String.format(message, objects))); ChatColor.translateAlternateColorCodes('&', String.format(message, objects)));
} }
@Override @Override
@@ -44,9 +43,9 @@ public class BukkitCommandExecutor implements CommandExecutor {
@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
@@ -22,6 +22,7 @@ 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;
@@ -33,14 +34,11 @@ 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() Bukkit.getPluginManager().registerEvents(this, BukkitPlugin.pluginInstance.getPlugin());
.registerEvents(this, BukkitPlugin.pluginInstance.getPlugin());
} }
@Override @Override
@@ -62,8 +60,9 @@ public class BukkitListener extends VPNExecutor implements Listener {
public void runCommand(String command) { public void runCommand(String command) {
new BukkitRunnable() { new BukkitRunnable() {
public void run() { public void run() {
Bukkit.getServer().dispatchCommand(Bukkit.getConsoleSender(), Bukkit.getServer()
ChatColor.translateAlternateColorCodes('&', command)); .dispatchCommand(
Bukkit.getConsoleSender(), ChatColor.translateAlternateColorCodes('&', command));
} }
}.runTask(BukkitPlugin.pluginInstance.getPlugin()); }.runTask(BukkitPlugin.pluginInstance.getPlugin());
} }
@@ -76,41 +75,44 @@ public class BukkitListener extends VPNExecutor implements Listener {
@EventHandler(priority = EventPriority.HIGH) @EventHandler(priority = EventPriority.HIGH)
public void onLogin(final PlayerLoginEvent event) { public void onLogin(final PlayerLoginEvent event) {
APIPlayer player = AntiVPN.getInstance().getPlayerExecutor().getPlayer(event.getPlayer().getUniqueId()) APIPlayer player =
.orElse(new OfflinePlayer( AntiVPN.getInstance()
.getPlayerExecutor()
.getPlayer(event.getPlayer().getUniqueId())
.orElse(
new OfflinePlayer(
event.getPlayer().getUniqueId(), event.getPlayer().getUniqueId(),
event.getPlayer().getName(), event.getPlayer().getName(),
event.getAddress() event.getAddress()));
));
CheckResult result = player.checkPlayer(); CheckResult result = player.checkPlayer();
if(!result.resultType().isShouldBlock()) return; if (!result.resultType().isShouldBlock()) return;
if(!AntiVPN.getInstance().getVpnConfig().isKickPlayers()) { if (!AntiVPN.getInstance().getVpnConfig().isKickPlayers()) {
return; return;
} }
event.setResult(PlayerLoginEvent.Result.KICK_BANNED); event.setResult(PlayerLoginEvent.Result.KICK_BANNED);
event.setKickMessage(switch (result.resultType()) { event.setKickMessage(
case DENIED_COUNTRY -> StringUtil.varReplace( switch (result.resultType()) {
case DENIED_COUNTRY ->
StringUtil.varReplace(
AntiVPN.getInstance().getVpnConfig().getCountryVanillaKickReason(), AntiVPN.getInstance().getVpnConfig().getCountryVanillaKickReason(),
player, player,
result.response() result.response());
);
case DENIED_PROXY -> case DENIED_PROXY ->
StringUtil.varReplace( StringUtil.varReplace(
AntiVPN.getInstance().getVpnConfig().getKickMessage(), AntiVPN.getInstance().getVpnConfig().getKickMessage(), player, result.response());
player,
result.response()
);
default -> "You were kicked by KauriVPN for an unknown reason!"; 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().getPlayerExecutor().getPlayer(event.getPlayer().getUniqueId()) AntiVPN.getInstance()
.getPlayerExecutor()
.getPlayer(event.getPlayer().getUniqueId())
.ifPresent(APIPlayer::checkAlertsState); .ifPresent(APIPlayer::checkAlertsState);
} }
@@ -24,8 +24,12 @@ 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) { public BukkitPlayer(Player player) {
super(player.getUniqueId(), player.getName(), player.getAddress() != null ? player.getAddress().getAddress() : null); super(
player.getUniqueId(),
player.getName(),
player.getAddress() != null ? player.getAddress().getAddress() : null);
this.player = player; this.player = player;
} }
@@ -18,11 +18,10 @@ 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 org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import java.util.*; import java.util.*;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
public class BukkitPlayerExecutor implements PlayerExecutor { public class BukkitPlayerExecutor implements PlayerExecutor {
@@ -32,22 +31,24 @@ public class BukkitPlayerExecutor implements PlayerExecutor {
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(
cachedPlayers.computeIfAbsent(player.getUniqueId(), k -> new BukkitPlayer(player)));
} }
@Override @Override
public Optional<APIPlayer> getPlayer(UUID uuid) { public Optional<APIPlayer> getPlayer(UUID uuid) {
final Player player = Bukkit.getPlayer(uuid); final Player player = Bukkit.getPlayer(uuid);
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(
cachedPlayers.computeIfAbsent(player.getUniqueId(), k -> new BukkitPlayer(player)));
} }
@Override @Override
@@ -55,12 +56,10 @@ public class BukkitPlayerExecutor implements PlayerExecutor {
cachedPlayers.remove(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,6 +24,11 @@ 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;
@@ -34,28 +39,19 @@ 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 @Getter private File dataFolder;
private File dataFolder;
private final List<org.bukkit.command.Command> registeredCommands = new ArrayList<>(); private final List<org.bukkit.command.Command> registeredCommands = new ArrayList<>();
@Getter @Getter private final JavaPlugin plugin;
private final JavaPlugin plugin;
public BukkitPlugin(JavaPlugin plugin) { public BukkitPlugin(JavaPlugin plugin) {
this.plugin = plugin; this.plugin = plugin;
} }
@Getter @Getter private PlayerCommandRunner playerCommandRunner;
private PlayerCommandRunner playerCommandRunner;
@Override @Override
public void onLoad(File dataFolder) { public void onLoad(File dataFolder) {
@@ -72,7 +68,7 @@ public class BukkitPlugin implements LoaderBootstrap {
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));
@@ -90,7 +86,10 @@ public class BukkitPlugin implements LoaderBootstrap {
Field field = SimplePluginManager.class.getDeclaredField("commandMap"); Field field = SimplePluginManager.class.getDeclaredField("commandMap");
field.setAccessible(true); field.setAccessible(true);
commandMap = (SimpleCommandMap) field.get(manager); commandMap = (SimpleCommandMap) field.get(manager);
} catch (IllegalArgumentException | SecurityException | NoSuchFieldException | IllegalAccessException e) { } catch (IllegalArgumentException
| SecurityException
| NoSuchFieldException
| IllegalAccessException e) {
AntiVPN.getInstance().getExecutor().logException(e); AntiVPN.getInstance().getExecutor().logException(e);
} }
} }
@@ -107,7 +106,7 @@ public class BukkitPlugin implements LoaderBootstrap {
commandMap.register(plugin.getName(), newCommand); commandMap.register(plugin.getName(), newCommand);
} }
//TODO Finish system before implementing on startup // TODO Finish system before implementing on startup
/*Bukkit.getLogger().info("Getting strings..."); /*Bukkit.getLogger().info("Getting strings...");
AntiVPN.getInstance().getMessageHandler().initStrings(vpnString -> new ConfigDefault<> AntiVPN.getInstance().getMessageHandler().initStrings(vpnString -> new ConfigDefault<>
(vpnString.getDefaultMessage(), "messages." + vpnString.getKey(), BukkitPlugin.pluginInstance) (vpnString.getDefaultMessage(), "messages." + vpnString.getKey(), BukkitPlugin.pluginInstance)
@@ -129,8 +128,9 @@ public class BukkitPlugin implements LoaderBootstrap {
Field field = SimpleCommandMap.class.getDeclaredField("knownCommands"); Field field = SimpleCommandMap.class.getDeclaredField("knownCommands");
field.setAccessible(true); field.setAccessible(true);
if(field.get(commandMap) instanceof Map<?, ?> knownCommands) { if (field.get(commandMap) instanceof Map<?, ?> knownCommands) {
Map<String, org.bukkit.command.Command> casted = (Map<String, org.bukkit.command.Command>) knownCommands; Map<String, org.bukkit.command.Command> casted =
(Map<String, org.bukkit.command.Command>) knownCommands;
casted.values().removeAll(registeredCommands); casted.values().removeAll(registeredCommands);
registeredCommands.clear(); registeredCommands.clear();
} }
@@ -149,11 +149,11 @@ public class BukkitPlugin implements LoaderBootstrap {
private String getDatabaseType() { private String getDatabaseType() {
VPNDatabase database = AntiVPN.getInstance().getDatabase(); VPNDatabase database = AntiVPN.getInstance().getDatabase();
if(database instanceof MySqlVPN) { if (database instanceof MySqlVPN) {
return "MySQL"; return "MySQL";
} else if(database instanceof H2VPN) { } else if (database instanceof H2VPN) {
return "H2"; return "H2";
} else if(database instanceof MongoVPN) { } else if (database instanceof MongoVPN) {
return "MongoDB"; return "MongoDB";
} else { } else {
return "No-Database"; return "No-Database";
@@ -17,36 +17,36 @@
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 = Executors.newSingleThreadScheduledExecutor( executorService =
MiscUtils.createThreadFactory("AntiVPN:PlayerCommandRunner") Executors.newSingleThreadScheduledExecutor(
); 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();
@@ -56,7 +56,10 @@ public class PlayerCommandRunner {
playerActions.poll(); playerActions.poll();
} }
} }
}, 1000, 100, TimeUnit.MILLISECONDS); },
1000,
100,
TimeUnit.MILLISECONDS);
} }
void stop() { void stop() {
@@ -19,17 +19,17 @@ 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) { public BukkitCommand(Command command) {
super(command.name(), command.description(), command.usage(), Arrays.asList(command.aliases())); super(command.name(), command.description(), command.usage(), Arrays.asList(command.aliases()));
@@ -41,13 +41,17 @@ public class BukkitCommand extends org.bukkit.command.Command {
throws IllegalArgumentException { throws IllegalArgumentException {
val children = command.children(); val children = command.children();
if(children.length > 0 && args.length > 0) { if (children.length > 0 && args.length > 0) {
for (Command child : children) { for (Command child : children) {
if(child.name().equalsIgnoreCase(args[0]) || Arrays.stream(child.aliases()) if (child.name().equalsIgnoreCase(args[0])
|| Arrays.stream(child.aliases())
.anyMatch(alias2 -> alias2.equalsIgnoreCase(args[0]))) { .anyMatch(alias2 -> alias2.equalsIgnoreCase(args[0]))) {
return child.tabComplete(new BukkitCommandExecutor(sender), alias, IntStream return child.tabComplete(
.range(0, args.length - 1) new BukkitCommandExecutor(sender),
.mapToObj(i -> args[i + 1]).toArray(String[]::new)); alias,
IntStream.range(0, args.length - 1)
.mapToObj(i -> args[i + 1])
.toArray(String[]::new));
} }
} }
} }
@@ -56,37 +60,48 @@ public class BukkitCommand extends org.bukkit.command.Command {
@Override @Override
public boolean execute(CommandSender sender, String s, String[] args) { public boolean execute(CommandSender sender, String s, String[] args) {
if(!sender.hasPermission("antivpn.command.*") if (!sender.hasPermission("antivpn.command.*") && !sender.hasPermission(command.permission())) {
&& !sender.hasPermission(command.permission())) { sender.sendMessage(
sender.sendMessage(ChatColor.translateAlternateColorCodes('&', ChatColor.translateAlternateColorCodes(
'&',
AntiVPN.getInstance().getMessageHandler().getString("no-permission").getMessage())); AntiVPN.getInstance().getMessageHandler().getString("no-permission").getMessage()));
return true; return true;
} }
val children = command.children(); val children = command.children();
if(children.length > 0 && args.length > 0) { if (children.length > 0 && args.length > 0) {
for (Command child : children) { for (Command child : children) {
if(child.name().equalsIgnoreCase(args[0]) || Arrays.stream(child.aliases()) if (child.name().equalsIgnoreCase(args[0])
.anyMatch(alias -> alias.equalsIgnoreCase(args[0]))) { || Arrays.stream(child.aliases()).anyMatch(alias -> alias.equalsIgnoreCase(args[0]))) {
if(!sender.hasPermission("antivpn.command.*") if (!sender.hasPermission("antivpn.command.*")
&& !sender.hasPermission(child.permission())) { && !sender.hasPermission(child.permission())) {
sender.sendMessage(ChatColor.translateAlternateColorCodes('&', sender.sendMessage(
AntiVPN.getInstance().getMessageHandler().getString("no-permission").getMessage())); ChatColor.translateAlternateColorCodes(
'&',
AntiVPN.getInstance()
.getMessageHandler()
.getString("no-permission")
.getMessage()));
return true; return true;
} }
sender.sendMessage(ChatColor.translateAlternateColorCodes('&', sender.sendMessage(
child.execute(new BukkitCommandExecutor(sender), IntStream ChatColor.translateAlternateColorCodes(
.range(0, args.length - 1) '&',
.mapToObj(i -> args[i + 1]).toArray(String[]::new)))); child.execute(
new BukkitCommandExecutor(sender),
IntStream.range(0, args.length - 1)
.mapToObj(i -> args[i + 1])
.toArray(String[]::new))));
return true; return true;
} }
} }
} }
sender.sendMessage(ChatColor.translateAlternateColorCodes('&', sender.sendMessage(
command.execute(new BukkitCommandExecutor(sender), args))); ChatColor.translateAlternateColorCodes(
'&', command.execute(new BukkitCommandExecutor(sender), args)));
return true; return true;
} }
@@ -1,24 +1,20 @@
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.api.PlayerExecutor; import dev.brighten.antivpn.StandardTest;
import dev.brighten.antivpn.api.VPNConfig; import dev.brighten.antivpn.api.*;
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;
@@ -29,14 +25,16 @@ 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;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; public class BukkitListenerTest extends StandardTest {
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.*;
public class BukkitListenerTest {
private ServerMock server; private ServerMock server;
private BukkitListener listener; private BukkitListener listener;
@@ -45,10 +43,10 @@ public class BukkitListenerTest {
@BeforeEach @BeforeEach
public void setUp() throws Exception { public void setUp() throws Exception {
server = MockBukkit.mock(new RecordingServerMock()); server = MockBukkit.mock(new RecordingServerMock());
JavaPlugin plugin = MockBukkit.loadWith( JavaPlugin plugin =
MockBukkit.loadWith(
TestPlugin.class, TestPlugin.class,
new PluginDescriptionFile("AntiVPNTest", "1.0.0", TestPlugin.class.getName()) new PluginDescriptionFile("AntiVPNTest", "1.0.0", TestPlugin.class.getName()));
);
BukkitPlugin.pluginInstance = new BukkitPlugin(plugin); BukkitPlugin.pluginInstance = new BukkitPlugin(plugin);
AntiVPN antiVPN = mock(AntiVPN.class); AntiVPN antiVPN = mock(AntiVPN.class);
@@ -72,10 +70,17 @@ public class BukkitListenerTest {
when(mockVpnString.getFormattedMessage(any())).thenReturn("Blocked!"); when(mockVpnString.getFormattedMessage(any())).thenReturn("Blocked!");
when(messageHandler.getString(anyString())).thenReturn(mockVpnString); when(messageHandler.getString(anyString())).thenReturn(mockVpnString);
when(vpnExecutor.checkIp(anyString())).thenReturn(CompletableFuture.completedFuture( when(vpnExecutor.checkIp(anyString()))
VPNResponse.builder().success(true).proxy(false).ip("127.0.0.1") .thenReturn(
.method("N/A").countryName("N/A").city("N/A").build() CompletableFuture.completedFuture(
)); 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 // Use reflection to set the private static INSTANCE field
Field instanceField = AntiVPN.class.getDeclaredField("INSTANCE"); Field instanceField = AntiVPN.class.getDeclaredField("INSTANCE");
@@ -101,6 +106,21 @@ public class BukkitListenerTest {
PlayerMock player = server.addPlayer("TestPlayer"); PlayerMock player = server.addPlayer("TestPlayer");
InetAddress address = InetAddress.getByName("127.0.0.1"); 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); PlayerLoginEvent event = new PlayerLoginEvent(player, "localhost", address);
listener.onLogin(event); listener.onLogin(event);
@@ -108,41 +128,21 @@ public class BukkitListenerTest {
assertEquals(PlayerLoginEvent.Result.ALLOWED, event.getResult()); assertEquals(PlayerLoginEvent.Result.ALLOWED, event.getResult());
} }
@Test
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 @Test
public void testLoginPipelineProxyPlayerIsKickedWithoutErrors() throws Exception { public void testLoginPipelineProxyPlayerIsKickedWithoutErrors() throws Exception {
PlayerMock player = server.addPlayer("PipelineProxyPlayer"); PlayerMock player = server.addPlayer("PipelineProxyPlayer");
InetAddress address = InetAddress.getByName("1.1.1.1"); InetAddress address = InetAddress.getByName("1.1.1.1");
when(vpnExecutor.checkIp("1.1.1.1")).thenReturn(CompletableFuture.completedFuture( mockCache();
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); PlayerLoginEvent event = new PlayerLoginEvent(player, "localhost", address);
assertDoesNotThrow(() -> listener.onLogin(event)); assertDoesNotThrow(() -> listener.onLogin(event));
assertEquals(PlayerLoginEvent.Result.KICK_BANNED, event.getResult()); assertEquals(PlayerLoginEvent.Result.KICK_BANNED, event.getResult());
assertEquals("Blocked!", net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().serialize(event.kickMessage())); assertEquals(
"Blocked!",
net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection()
.serialize(event.kickMessage()));
} }
@Test @Test
@@ -151,26 +151,29 @@ public class BukkitListenerTest {
ExecutorService executor = Executors.newSingleThreadExecutor(); ExecutorService executor = Executors.newSingleThreadExecutor();
try { try {
CompletableFuture<Void> asyncCall = CompletableFuture.runAsync( CompletableFuture<Void> asyncCall =
() -> listener.runCommand("antivpn-test &aok"), CompletableFuture.runAsync(() -> listener.runCommand("antivpn-test &aok"), executor);
executor
);
assertDoesNotThrow(() -> asyncCall.get(5, TimeUnit.SECONDS)); assertDoesNotThrow(() -> asyncCall.get(5, TimeUnit.SECONDS));
assertFalse(recordingServer.commandDispatched(), "Command should be scheduled, not dispatched asynchronously"); assertFalse(
recordingServer.commandDispatched(),
"Command should be scheduled, not dispatched asynchronously");
server.getScheduler().performOneTick(); server.getScheduler().performOneTick();
assertTrue(recordingServer.commandDispatched(), "Scheduled command should be dispatched on the next server tick"); assertTrue(
assertTrue(recordingServer.dispatchedOnPrimaryThread(), "Command dispatch must happen on Bukkit's primary thread"); 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()); assertEquals("antivpn-test §aok", recordingServer.dispatchedCommand());
} finally { } finally {
executor.shutdownNow(); executor.shutdownNow();
} }
} }
public static class TestPlugin extends JavaPlugin { public static class TestPlugin extends JavaPlugin {}
}
private static class RecordingServerMock extends ServerMock { private static class RecordingServerMock extends ServerMock {
private final AtomicBoolean commandDispatched = new AtomicBoolean(); private final AtomicBoolean commandDispatched = new AtomicBoolean();
@@ -1,22 +1,21 @@
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;
@@ -44,7 +43,8 @@ class BukkitPlayerTest {
assertTrue(player.isOnline()); assertTrue(player.isOnline());
CompletableFuture<Void> asyncKick = CompletableFuture.runAsync(() -> bukkitPlayer.kickPlayer("&cBlocked!")); CompletableFuture<Void> asyncKick =
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");
+4
View File
@@ -29,4 +29,8 @@ 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
@@ -29,7 +29,8 @@ public class BungeeLoaderPlugin extends Plugin {
private final LoaderBootstrap plugin; private final LoaderBootstrap plugin;
public BungeeLoaderPlugin() { public BungeeLoaderPlugin() {
JarInJarClassLoader loader = new JarInJarClassLoader(getClass().getClassLoader(), JAR_NAME, SOURCE_NAME); JarInJarClassLoader loader =
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);
} }
@@ -47,6 +48,4 @@ public class BungeeLoaderPlugin extends Plugin {
public void onDisable() { public void onDisable() {
this.plugin.onDisable(); this.plugin.onDisable();
} }
} }
+1
View File
@@ -13,6 +13,7 @@ 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,6 +20,9 @@ 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;
@@ -29,17 +32,15 @@ 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.getProxy().getPluginManager() BungeePlugin.pluginInstance
.getProxy()
.getPluginManager()
.registerListener(BungeePlugin.pluginInstance.getPlugin(), this); .registerListener(BungeePlugin.pluginInstance.getPlugin(), this);
} }
@@ -60,30 +61,49 @@ public class BungeeListener extends VPNExecutor implements Listener {
@Override @Override
public void runCommand(String command) { public void runCommand(String command) {
BungeePlugin.pluginInstance.getProxy().getPluginManager() BungeePlugin.pluginInstance
.getProxy()
.getPluginManager()
.dispatchCommand(BungeePlugin.pluginInstance.getProxy().getConsole(), command); .dispatchCommand(BungeePlugin.pluginInstance.getProxy().getConsole(), command);
} }
@Override @Override
public void disablePlugin() { public void disablePlugin() {
BungeePlugin.pluginInstance.getProxy().getPluginManager().unregisterListeners(BungeePlugin.pluginInstance.getPlugin()); BungeePlugin.pluginInstance
.getProxy()
.getPluginManager()
.unregisterListeners(BungeePlugin.pluginInstance.getPlugin());
if (cacheResetTask != null) { if (cacheResetTask != null) {
cacheResetTask.cancel(); cacheResetTask.cancel();
cacheResetTask = null; cacheResetTask = null;
} }
BungeePlugin.pluginInstance.getProxy().getPluginManager().unregisterCommands(BungeePlugin.pluginInstance.getPlugin()); BungeePlugin.pluginInstance
.getProxy()
.getPluginManager()
.unregisterCommands(BungeePlugin.pluginInstance.getPlugin());
BungeePlugin.pluginInstance.onDisable(); BungeePlugin.pluginInstance.onDisable();
} }
@EventHandler(priority = EventPriority.HIGH) @EventHandler(priority = EventPriority.HIGH)
public void onListener(final PreLoginEvent event) { public void onListener(final PreLoginEvent event) {
APIPlayer player = AntiVPN.getInstance().getPlayerExecutor().getPlayer(event.getConnection().getUniqueId()) APIPlayer player =
.orElseGet(() -> { AntiVPN.getInstance()
.getPlayerExecutor()
.getPlayer(event.getConnection().getUniqueId())
.orElseGet(
() -> {
UUID uuid = MiscUtils.lookupUUID(event.getConnection().getName()); UUID uuid = MiscUtils.lookupUUID(event.getConnection().getName());
AntiVPN.getInstance().getExecutor().log(Level.INFO, "Getting offline player for %s with name %s", AntiVPN.getInstance()
event.getConnection().getUniqueId(), uuid); .getExecutor()
.log(
Level.INFO,
"Getting offline player for %s with name %s",
event.getConnection().getUniqueId(),
uuid);
return new OfflinePlayer(uuid, event.getConnection().getName(), return new OfflinePlayer(
uuid,
event.getConnection().getName(),
((InetSocketAddress) event.getConnection().getSocketAddress()).getAddress()); ((InetSocketAddress) event.getConnection().getSocketAddress()).getAddress());
}); });
@@ -91,26 +111,39 @@ public class BungeeListener extends VPNExecutor implements Listener {
if (!result.resultType().isShouldBlock()) return; if (!result.resultType().isShouldBlock()) return;
if(!AntiVPN.getInstance().getVpnConfig().isKickPlayers()) { if (!AntiVPN.getInstance().getVpnConfig().isKickPlayers()) {
return; return;
} }
event.setCancelled(true); event.setCancelled(true);
event.setReason(TextComponent.fromLegacy(StringUtil.varReplace(switch (result.resultType()) { event.setReason(
case DENIED_PROXY -> StringUtil.varReplace(AntiVPN.getInstance().getVpnConfig() TextComponent.fromLegacy(
.getKickMessage(), player, result.response()); StringUtil.varReplace(
case DENIED_COUNTRY -> StringUtil.varReplace(AntiVPN.getInstance().getVpnConfig() switch (result.resultType()) {
.getCountryVanillaKickReason(), player, result.response()); 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!"; default -> "You were kicked by KauriVPN for an unknown reason!";
}, player, result.response()))); },
player,
result.response())));
} }
@EventHandler(priority = EventPriority.HIGH) @EventHandler(priority = EventPriority.HIGH)
public void onJoin(LoginEvent event) { public void onJoin(LoginEvent event) {
if(event.isCancelled()) return; if (event.isCancelled()) return;
// Handling player alerts on join // Handling player alerts on join
AntiVPN.getInstance().getPlayerExecutor().getPlayer(event.getConnection().getUniqueId()) AntiVPN.getInstance()
.getPlayerExecutor()
.getPlayer(event.getConnection().getUniqueId())
.ifPresent(APIPlayer::checkAlertsState); .ifPresent(APIPlayer::checkAlertsState);
} }
@@ -24,23 +24,23 @@ 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) { public BungeePlayer(ProxiedPlayer player) {
super(player.getUniqueId(), player.getName(), player.getAddress().getAddress()); super(player.getUniqueId(), player.getName(), player.getAddress().getAddress());
this.player = player; this.player = player;
} }
@Override @Override
public void sendMessage(String message) { public void sendMessage(String message) {
player.sendMessage(TextComponent.fromLegacyText(ChatColor player.sendMessage(
.translateAlternateColorCodes('&', message))); TextComponent.fromLegacyText(ChatColor.translateAlternateColorCodes('&', message)));
} }
@Override @Override
public void kickPlayer(String reason) { public void kickPlayer(String reason) {
player.disconnect(TextComponent.fromLegacyText(ChatColor player.disconnect(
.translateAlternateColorCodes('&', reason))); TextComponent.fromLegacyText(ChatColor.translateAlternateColorCodes('&', reason)));
} }
@Override @Override
@@ -18,10 +18,9 @@ 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 {
@@ -31,16 +30,17 @@ public class BungeePlayerExecutor implements PlayerExecutor {
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(cachedPlayers.computeIfAbsent(player.getUniqueId(), key -> new BungeePlayer(player))); return Optional.of(
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)));
} }
@@ -24,24 +24,21 @@ 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 @Getter private File dataFolder;
private File dataFolder;
@Getter @Getter private final Plugin plugin;
private final Plugin plugin;
public BungeePlugin(Plugin plugin) { public BungeePlugin(Plugin plugin) {
this.plugin = plugin; this.plugin = plugin;
@@ -56,25 +53,31 @@ public class BungeePlugin implements LoaderBootstrap {
public void onEnable() { public void onEnable() {
pluginInstance = this; pluginInstance = this;
//Setting up config // Setting up config
ProxyServer.getInstance().getLogger().info("Loading config..."); ProxyServer.getInstance().getLogger().info("Loading config...");
// Loading plugin
//Loading plugin
ProxyServer.getInstance().getLogger().info("Starting AntiVPN services..."); ProxyServer.getInstance().getLogger().info("Starting AntiVPN services...");
AntiVPN.start(new BungeeListener(), new BungeePlayerExecutor(), getDataFolder()); AntiVPN.start(new BungeeListener(), new BungeePlayerExecutor(), getDataFolder());
if(AntiVPN.getInstance().getVpnConfig().metrics()) { if (AntiVPN.getInstance().getVpnConfig().metrics()) {
ProxyServer.getInstance().getLogger().info("Starting bStats metrics..."); ProxyServer.getInstance().getLogger().info("Starting bStats metrics...");
Metrics metrics = new Metrics(getPlugin(), 12616); Metrics metrics = new Metrics(getPlugin(), 12616);
metrics.addCustomChart(new SimplePie("database_used", this::getDatabaseType)); metrics.addCustomChart(new SimplePie("database_used", this::getDatabaseType));
ProxyServer.getInstance().getScheduler().schedule(getPlugin(), ProxyServer.getInstance()
.getScheduler()
.schedule(
getPlugin(),
() -> AntiVPN.getInstance().checked = AntiVPN.getInstance().detections = 0, () -> AntiVPN.getInstance().checked = AntiVPN.getInstance().detections = 0,
10, 10, TimeUnit.MINUTES); 10,
10,
TimeUnit.MINUTES);
} }
for (Command command : AntiVPN.getInstance().getCommands()) { for (Command command : AntiVPN.getInstance().getCommands()) {
ProxyServer.getInstance().getPluginManager().registerCommand(getPlugin(), new BungeeCommand(command)); ProxyServer.getInstance()
.getPluginManager()
.registerCommand(getPlugin(), new BungeeCommand(command));
} }
} }
@@ -85,11 +88,11 @@ public class BungeePlugin implements LoaderBootstrap {
private String getDatabaseType() { private String getDatabaseType() {
VPNDatabase database = AntiVPN.getInstance().getDatabase(); VPNDatabase database = AntiVPN.getInstance().getDatabase();
if(database instanceof MySqlVPN) { if (database instanceof MySqlVPN) {
return "MySQL"; return "MySQL";
} else if(database instanceof H2VPN) { } else if (database instanceof H2VPN) {
return "H2"; return "H2";
} else if(database instanceof MongoVPN) { } else if (database instanceof MongoVPN) {
return "MongoDB"; return "MongoDB";
} else { } else {
return "No-Database"; return "No-Database";
@@ -17,6 +17,8 @@
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;
@@ -24,12 +26,10 @@ 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) { public BungeeCommand(dev.brighten.antivpn.command.Command command) {
super(command.name(), command.permission(), command.aliases()); super(command.name(), command.permission(), command.aliases());
@@ -38,55 +38,72 @@ public class BungeeCommand extends Command implements TabExecutor {
@Override @Override
public void execute(CommandSender sender, String[] args) { public void execute(CommandSender sender, String[] args) {
if(!sender.hasPermission("antivpn.command.*") if (!sender.hasPermission("antivpn.command.*") && !sender.hasPermission(command.permission())) {
&& !sender.hasPermission(command.permission())) { sender.sendMessage(
sender.sendMessage(TextComponent.fromLegacyText(ChatColor.translateAlternateColorCodes('&', TextComponent.fromLegacyText(
AntiVPN.getInstance().getMessageHandler().getString("no-permission").getMessage()))); ChatColor.translateAlternateColorCodes(
'&',
AntiVPN.getInstance()
.getMessageHandler()
.getString("no-permission")
.getMessage())));
return; return;
} }
val children = command.children(); val children = command.children();
if(children.length > 0 && args.length > 0) { if (children.length > 0 && args.length > 0) {
for (dev.brighten.antivpn.command.Command child : children) { for (dev.brighten.antivpn.command.Command child : children) {
if(child.name().equalsIgnoreCase(args[0]) || Arrays.stream(child.aliases()) if (child.name().equalsIgnoreCase(args[0])
.anyMatch(alias -> alias.equalsIgnoreCase(args[0]))) { || Arrays.stream(child.aliases()).anyMatch(alias -> alias.equalsIgnoreCase(args[0]))) {
if(!sender.hasPermission("antivpn.command.*") if (!sender.hasPermission("antivpn.command.*")
&& !sender.hasPermission(child.permission())) { && !sender.hasPermission(child.permission())) {
sender.sendMessage(TextComponent.fromLegacyText(ChatColor.translateAlternateColorCodes('&', sender.sendMessage(
AntiVPN.getInstance().getMessageHandler().getString("no-permission").getMessage()))); TextComponent.fromLegacyText(
ChatColor.translateAlternateColorCodes(
'&',
AntiVPN.getInstance()
.getMessageHandler()
.getString("no-permission")
.getMessage())));
return; return;
} }
sender.sendMessage(TextComponent sender.sendMessage(
.fromLegacyText(ChatColor TextComponent.fromLegacyText(
.translateAlternateColorCodes('&', ChatColor.translateAlternateColorCodes(
child.execute(new BungeeCommandExecutor(sender), IntStream '&',
.range(0, args.length - 1) child.execute(
.mapToObj(i -> args[i + 1]).toArray(String[]::new))))); new BungeeCommandExecutor(sender),
IntStream.range(0, args.length - 1)
.mapToObj(i -> args[i + 1])
.toArray(String[]::new)))));
return; return;
} }
} }
} }
sender.sendMessage(
sender.sendMessage(TextComponent TextComponent.fromLegacyText(
.fromLegacyText(ChatColor ChatColor.translateAlternateColorCodes(
.translateAlternateColorCodes('&', '&', command.execute(new BungeeCommandExecutor(sender), args))));
command.execute(new BungeeCommandExecutor(sender), args))));
} }
@Override @Override
public Iterable<String> onTabComplete(CommandSender sender, String[] args) { public Iterable<String> onTabComplete(CommandSender sender, String[] args) {
val children = command.children(); val children = command.children();
if(children.length > 0 && args.length > 0) { if (children.length > 0 && args.length > 0) {
for (dev.brighten.antivpn.command.Command child : children) { for (dev.brighten.antivpn.command.Command child : children) {
if(child.name().equalsIgnoreCase(args[0]) || Arrays.stream(child.aliases()) if (child.name().equalsIgnoreCase(args[0])
|| Arrays.stream(child.aliases())
.anyMatch(alias2 -> alias2.equalsIgnoreCase(args[0]))) { .anyMatch(alias2 -> alias2.equalsIgnoreCase(args[0]))) {
return child.tabComplete(new BungeeCommandExecutor(sender), "alias", IntStream return child.tabComplete(
.range(0, args.length - 1) new BungeeCommandExecutor(sender),
.mapToObj(i -> args[i + 1]).toArray(String[]::new)); "alias",
IntStream.range(0, args.length - 1)
.mapToObj(i -> args[i + 1])
.toArray(String[]::new));
} }
} }
} }
@@ -19,14 +19,13 @@ 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 {
@@ -34,8 +33,9 @@ public class BungeeCommandExecutor implements CommandExecutor {
@Override @Override
public void sendMessage(String message, Object... objects) { public void sendMessage(String message, Object... objects) {
sender.sendMessage(TextComponent.fromLegacyText(ChatColor sender.sendMessage(
.translateAlternateColorCodes('&', String.format(message, objects)))); TextComponent.fromLegacyText(
ChatColor.translateAlternateColorCodes('&', String.format(message, objects))));
} }
@Override @Override
@@ -45,9 +45,11 @@ public class BungeeCommandExecutor implements CommandExecutor {
@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(((ProxiedPlayer) sender).getUniqueId()); return AntiVPN.getInstance()
.getPlayerExecutor()
.getPlayer(((ProxiedPlayer) sender).getUniqueId());
} }
@Override @Override
@@ -1,27 +1,27 @@
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;
import java.lang.reflect.Field; public class BungeeListenerTest extends StandardTest {
import java.net.InetSocketAddress;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import static org.mockito.Mockito.*;
public class BungeeListenerTest {
private BungeeListener listener; private BungeeListener listener;
private VPNExecutor vpnExecutor; private VPNExecutor vpnExecutor;
@@ -49,10 +49,17 @@ public class BungeeListenerTest {
when(mockVpnString.getFormattedMessage(any())).thenReturn("Blocked!"); when(mockVpnString.getFormattedMessage(any())).thenReturn("Blocked!");
when(messageHandler.getString(anyString())).thenReturn(mockVpnString); when(messageHandler.getString(anyString())).thenReturn(mockVpnString);
when(vpnExecutor.checkIp(anyString())).thenReturn(CompletableFuture.completedFuture( when(vpnExecutor.checkIp(anyString()))
VPNResponse.builder().success(true).proxy(false).ip("127.0.0.1") .thenReturn(
.method("N/A").countryName("N/A").city("N/A").build() CompletableFuture.completedFuture(
)); 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 // Use reflection to set the private static INSTANCE field
Field instanceField = AntiVPN.class.getDeclaredField("INSTANCE"); Field instanceField = AntiVPN.class.getDeclaredField("INSTANCE");
@@ -86,7 +93,7 @@ public class BungeeListenerTest {
} }
@Test @Test
public void testPreLoginEventBlocked() { public void testPreLoginEventBlocked() throws NoSuchFieldException, IllegalAccessException {
PreLoginEvent event = mock(PreLoginEvent.class); PreLoginEvent event = mock(PreLoginEvent.class);
PendingConnection connection = mock(PendingConnection.class); PendingConnection connection = mock(PendingConnection.class);
@@ -97,10 +104,7 @@ public class BungeeListenerTest {
when(connection.getSocketAddress()).thenReturn(new InetSocketAddress("1.1.1.1", 12345)); when(connection.getSocketAddress()).thenReturn(new InetSocketAddress("1.1.1.1", 12345));
// Mock proxy response // Mock proxy response
when(vpnExecutor.checkIp("1.1.1.1")).thenReturn(CompletableFuture.completedFuture( mockCache();
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); listener.onListener(event);
+8
View File
@@ -4,6 +4,14 @@ 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 ## [1.10.1.1] 2026-04-29
### Fixed ### Fixed
+2
View File
@@ -1,5 +1,6 @@
plugins { plugins {
id 'com.gradleup.shadow' id 'com.gradleup.shadow'
id 'java-test-fixtures'
} }
dependencies { dependencies {
@@ -24,6 +25,7 @@ dependencies {
testImplementation 'com.h2database:h2:2.4.240' testImplementation 'com.h2database:h2:2.4.240'
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 {
@@ -33,10 +33,6 @@ 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;
@@ -44,16 +40,15 @@ 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.4.240")
@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( @MavenLibrary(groupId = "com.mysql", artifactId = "mysql-connector-j", version = "9.3.0")
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;
@@ -68,7 +63,7 @@ public class AntiVPN {
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();
@@ -80,16 +75,17 @@ public class AntiVPN {
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); MiscUtils.copy(INSTANCE.getResource("config.yml"), configFile);
} }
INSTANCE.config = ConfigurationProvider.getProvider(YamlConfiguration.class) INSTANCE.config = ConfigurationProvider.getProvider(YamlConfiguration.class).load(configFile);
.load(configFile);
} catch (IOException e) { } catch (IOException e) {
AntiVPN.getInstance().getExecutor().logException("Could not load config.yml, plugin disabling...", e); AntiVPN.getInstance()
.getExecutor()
.logException("Could not load config.yml, plugin disabling...", e);
executor.disablePlugin(); executor.disablePlugin();
return; return;
} }
@@ -102,17 +98,19 @@ public class AntiVPN {
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..."); AntiVPN.getInstance().getExecutor().log("Using databaseType MySQL...");
INSTANCE.database = new MySqlVPN(); INSTANCE.database = new MySqlVPN();
INSTANCE.database.init(); INSTANCE.database.init();
@@ -120,38 +118,56 @@ public class AntiVPN {
} }
case "mongo": case "mongo":
case "mongodb": case "mongodb":
case "mongod": { case "mongod":
{
INSTANCE.database = new MongoVPN(); INSTANCE.database = new MongoVPN();
INSTANCE.database.init(); INSTANCE.database.init();
break; break;
} }
default: { default:
AntiVPN.getInstance().getExecutor().log("Could not find database type \"" + INSTANCE.vpnConfig.getDatabaseType() + "\". " + {
"Options: [MySQL]"); AntiVPN.getInstance()
.getExecutor()
.log(
"Could not find database type \""
+ INSTANCE.vpnConfig.getDatabaseType()
+ "\". "
+ "Options: [MySQL]");
break; break;
} }
} }
} catch (Exception e) { } catch (Exception e) {
AntiVPN.getInstance().getExecutor().logException("Could not initialize database, plugin disabling...", e); AntiVPN.getInstance()
.getExecutor()
.logException("Could not initialize database, plugin disabling...", e);
executor.disablePlugin(); executor.disablePlugin();
return; 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.getOnlinePlayers().forEach(player -> { playerExecutor
//We want to make sure they even have permission to see alerts before we make a bunch .getOnlinePlayers()
//of unnecessary database queries. .forEach(
if(player.hasPermission("antivpn.command.alerts")) { player -> {
//Running database check for enabled alerts. // We want to make sure they even have permission to see alerts before we make a bunch
// 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().getMessageHandler().initStrings(vpnString -> new ConfigDefault<> AntiVPN.getInstance()
(vpnString.getDefaultMessage(), "messages." + vpnString.getKey(), AntiVPN.getInstance()) .getMessageHandler()
.initStrings(
vpnString ->
new ConfigDefault<>(
vpnString.getDefaultMessage(),
"messages." + vpnString.getKey(),
AntiVPN.getInstance())
.get()); .get());
AntiVPN.getInstance().getMessageHandler().reloadStrings(); AntiVPN.getInstance().getMessageHandler().reloadStrings();
@@ -196,7 +212,7 @@ public class AntiVPN {
if (executor != null && executor.getThreadExecutor() != null) { if (executor != null && executor.getThreadExecutor() != null) {
executor.getThreadExecutor().shutdown(); executor.getThreadExecutor().shutdown();
} }
if(database != null) database.shutdown(); if (database != null) database.shutdown();
INSTANCE = null; INSTANCE = null;
} }
@@ -204,17 +220,19 @@ public class AntiVPN {
public void reloadDatabase() { public void reloadDatabase() {
database.shutdown(); database.shutdown();
switch(AntiVPN.getInstance().getVpnConfig().getDatabaseType().toLowerCase()) { switch (AntiVPN.getInstance().getVpnConfig().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..."); AntiVPN.getInstance().getExecutor().log("Using databaseType MySQL...");
INSTANCE.database = new MySqlVPN(); INSTANCE.database = new MySqlVPN();
INSTANCE.database.init(); INSTANCE.database.init();
@@ -222,21 +240,28 @@ public class AntiVPN {
} }
case "mongo": case "mongo":
case "mongodb": case "mongodb":
case "mongod": { case "mongod":
{
INSTANCE.database = new MongoVPN(); INSTANCE.database = new MongoVPN();
INSTANCE.database.init(); INSTANCE.database.init();
break; break;
} }
default: { default:
AntiVPN.getInstance().getExecutor().log("Could not find database type \"" + INSTANCE.vpnConfig.getDatabaseType() + "\". " + {
"Options: [MySQL]"); AntiVPN.getInstance()
.getExecutor()
.log(
"Could not find database type \""
+ INSTANCE.vpnConfig.getDatabaseType()
+ "\". "
+ "Options: [MySQL]");
break; break;
} }
} }
} }
public static AntiVPN getInstance() { public static AntiVPN getInstance() {
assert INSTANCE != null: "AntiVPN has not been initialized!"; assert INSTANCE != null : "AntiVPN has not been initialized!";
return INSTANCE; return INSTANCE;
} }
@@ -253,7 +278,8 @@ public class AntiVPN {
public void reloadConfig() { public void reloadConfig() {
try { try {
config = ConfigurationProvider.getProvider(YamlConfiguration.class) config =
ConfigurationProvider.getProvider(YamlConfiguration.class)
.load(new File(pluginFolder.getPath() + File.separator + "config.yml")); .load(new File(pluginFolder.getPath() + File.separator + "config.yml"));
} catch (IOException e) { } catch (IOException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
@@ -20,26 +20,22 @@ 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 lombok.Getter;
import lombok.Setter;
import java.net.InetAddress; import java.net.InetAddress;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.logging.Level; import java.util.logging.Level;
import lombok.Getter;
import lombok.Setter;
@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 @Setter private boolean alertsEnabled;
private boolean alertsEnabled;
private static final Cache<String, CheckResult> checkResultCache = Caffeine.newBuilder() public static final Cache<String, CheckResult> checkResultCache =
.expireAfterWrite(5, TimeUnit.MINUTES) Caffeine.newBuilder().expireAfterWrite(5, TimeUnit.MINUTES).maximumSize(2000).build();
.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;
@@ -54,23 +50,32 @@ public abstract class APIPlayer {
public abstract boolean hasPermission(String permission); public abstract boolean hasPermission(String permission);
public void updateAlertsState() { public void updateAlertsState() {
//Updating into database so its synced across servers and saved on logout. // Updating into database so its synced across servers and saved on logout.
AntiVPN.getInstance().getDatabase().updateAlertsState(uuid, alertsEnabled); AntiVPN.getInstance().getDatabase().updateAlertsState(uuid, alertsEnabled);
sendMessage(AntiVPN.getInstance().getMessageHandler() sendMessage(
AntiVPN.getInstance()
.getMessageHandler()
.getString("command-alerts-toggled") .getString("command-alerts-toggled")
.getFormattedMessage(new VpnString.Var<>("state", alertsEnabled))); .getFormattedMessage(new VpnString.Var<>("state", alertsEnabled)));
} }
public void checkAlertsState() { public void checkAlertsState() {
AntiVPN.getInstance().getExecutor().getThreadExecutor().execute(() -> AntiVPN.getInstance()
AntiVPN.getInstance().getDatabase().alertsState(uuid, state -> { .getExecutor()
if(state) { .getThreadExecutor()
.execute(
() ->
AntiVPN.getInstance()
.getDatabase()
.alertsState(
uuid,
state -> {
if (state) {
alertsEnabled = true; alertsEnabled = true;
updateAlertsState(); updateAlertsState();
} }
}) }));
);
} }
/*** /***
@@ -79,10 +84,10 @@ public abstract class APIPlayer {
* @return CheckResult - The cached result of the check if it exists. * @return CheckResult - The cached result of the check if it exists.
*/ */
public CheckResult checkPlayer() { public CheckResult checkPlayer() {
if (hasPermission("antivpn.bypass") //Has bypass permission if (hasPermission("antivpn.bypass") // Has bypass permission
//Is exempt // Is exempt
|| (uuid != null && AntiVPN.getInstance().getExecutor().isWhitelisted(uuid)) || (uuid != null && AntiVPN.getInstance().getExecutor().isWhitelisted(uuid))
//Or has a name that starts with a certain prefix. This is for Bedrock exempting. // Or has a name that starts with a certain prefix. This is for Bedrock exempting.
|| AntiVPN.getInstance().getExecutor().isWhitelisted(ip.getHostAddress() + "/32") || AntiVPN.getInstance().getExecutor().isWhitelisted(ip.getHostAddress() + "/32")
|| AntiVPN.getInstance().getVpnConfig().getPrefixWhitelists().stream() || AntiVPN.getInstance().getVpnConfig().getPrefixWhitelists().stream()
.anyMatch(name::startsWith)) { .anyMatch(name::startsWith)) {
@@ -91,40 +96,55 @@ public abstract class APIPlayer {
CheckResult cachedResult = checkResultCache.getIfPresent(ip.getHostAddress()); CheckResult cachedResult = checkResultCache.getIfPresent(ip.getHostAddress());
if(cachedResult != null) { if (cachedResult != null) {
if(cachedResult.response().getIp().equals(ip.getHostAddress())) { if (cachedResult.response().getIp().equals(ip.getHostAddress())) {
AntiVPN.getInstance().getExecutor().log(Level.FINE, "Cached result for " + ip.getHostAddress() + " is " + cachedResult.resultType()); AntiVPN.getInstance()
if(cachedResult.resultType().isShouldBlock()) { .getExecutor()
.log(
Level.FINE,
"Cached result for " + ip.getHostAddress() + " is " + cachedResult.resultType());
if (cachedResult.resultType().isShouldBlock()) {
AntiVPN.getInstance().getExecutor().handleKickingOfPlayer(cachedResult, this); AntiVPN.getInstance().getExecutor().handleKickingOfPlayer(cachedResult, this);
} }
return cachedResult; return cachedResult;
} }
} }
AntiVPN.getInstance().getExecutor().checkIp(ip.getHostAddress()) AntiVPN.getInstance()
.thenAccept(result -> { .getExecutor()
if(!result.isSuccess()) { .checkIp(ip.getHostAddress())
AntiVPN.getInstance().getExecutor().log(Level.WARNING, "The API query was not a success! " + .thenAccept(
"You may need to upgrade your license on " + result -> {
"https://funkemunky.cc/shop"); if (!result.isSuccess()) {
AntiVPN.getInstance()
.getExecutor()
.log(
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. // If the countryList() size is zero, no need to check.
// Running country check first // Running country check first
CheckResult checkResult; CheckResult checkResult;
if (!AntiVPN.getInstance().getVpnConfig().getCountryList().isEmpty() if (!AntiVPN.getInstance().getVpnConfig().getCountryList().isEmpty()
&& !((uuid != null && AntiVPN.getInstance().getExecutor() && !((uuid != null && AntiVPN.getInstance().getExecutor().isWhitelisted(uuid))
.isWhitelisted(uuid)) // Or has a name that starts with a certain prefix. This is for Bedrock
//Or has a name that starts with a certain prefix. This is for Bedrock exempting. // exempting.
|| AntiVPN.getInstance().getExecutor().isWhitelisted(ip.getHostAddress() + "/32")) || AntiVPN.getInstance()
.getExecutor()
.isWhitelisted(ip.getHostAddress() + "/32"))
// This bit of code will decide whether or not to kick the player // 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 // 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 // as they are equal and vise versa. However, if the contains does not match
// the state, it will kick. // the state, it will kick.
&& AntiVPN.getInstance().getVpnConfig().getCountryList() && AntiVPN.getInstance()
.getVpnConfig()
.getCountryList()
.contains(result.getCountryCode()) .contains(result.getCountryCode())
!= AntiVPN.getInstance().getVpnConfig().getWhitelistCountries()) { != AntiVPN.getInstance().getVpnConfig().getWhitelistCountries()) {
//Using our built in kicking system if no commands are configured // Using our built in kicking system if no commands are configured
checkResult = new CheckResult(result, ResultType.DENIED_COUNTRY, false); checkResult = new CheckResult(result, ResultType.DENIED_COUNTRY, false);
} else if (result.isProxy()) { } else if (result.isProxy()) {
checkResult = new CheckResult(result, ResultType.DENIED_PROXY, false); checkResult = new CheckResult(result, ResultType.DENIED_PROXY, false);
@@ -132,10 +152,16 @@ public abstract class APIPlayer {
checkResult = new CheckResult(result, ResultType.ALLOWED, false); checkResult = new CheckResult(result, ResultType.ALLOWED, false);
} }
AntiVPN.getInstance().getExecutor().log(Level.FINE, "Result for " + ip.getHostAddress() + " is " + checkResult.resultType()); AntiVPN.getInstance()
.getExecutor()
.log(
Level.FINE,
"Result for " + ip.getHostAddress() + " is " + checkResult.resultType());
checkResultCache.put(ip.getHostAddress(), new CheckResult(checkResult.response(), checkResult.resultType(), true)); checkResultCache.put(
if(checkResult.resultType().isShouldBlock()) { ip.getHostAddress(),
new CheckResult(checkResult.response(), checkResult.resultType(), true));
if (checkResult.resultType().isShouldBlock()) {
AntiVPN.getInstance().getExecutor().handleKickingOfPlayer(checkResult, this); AntiVPN.getInstance().getExecutor().handleKickingOfPlayer(checkResult, this);
} }
AntiVPN.getInstance().checked++; AntiVPN.getInstance().checked++;
@@ -18,5 +18,4 @@ 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) {}
}
@@ -26,14 +26,10 @@ public class OfflinePlayer extends APIPlayer {
} }
@Override @Override
public void sendMessage(String message) { public void sendMessage(String message) {}
}
@Override @Override
public void kickPlayer(String reason) { public void kickPlayer(String reason) {}
}
@Override @Override
public boolean hasPermission(String permission) { public boolean hasPermission(String permission) {
@@ -26,8 +26,7 @@ public enum ResultType {
API_FAILURE(false), API_FAILURE(false),
UNKNOWN(false); UNKNOWN(false);
@Getter @Getter private final boolean shouldBlock;
private final boolean shouldBlock;
ResultType(boolean shouldBlock) { ResultType(boolean shouldBlock) {
this.shouldBlock = shouldBlock; this.shouldBlock = shouldBlock;
@@ -18,97 +18,87 @@ 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> licenseDefault = new ConfigDefault<>("", private final ConfigDefault<String>
"license", AntiVPN.getInstance()), kickStringDefault = licenseDefault = new ConfigDefault<>("", "license", AntiVPN.getInstance()),
new ConfigDefault<>("Proxies are not allowed on our server", kickStringDefault =
"kickMessage", AntiVPN.getInstance()), new ConfigDefault<>(
defaultDatabaseType = new ConfigDefault<>("H2", "Proxies are not allowed on our server", "kickMessage", AntiVPN.getInstance()),
"database.type", AntiVPN.getInstance()), defaultDatabaseType = new ConfigDefault<>("H2", "database.type", AntiVPN.getInstance()),
defaultDatabaseName = new ConfigDefault<>("kaurivpn", defaultDatabaseName =
"database.database", AntiVPN.getInstance()), new ConfigDefault<>("kaurivpn", "database.database", AntiVPN.getInstance()),
defaultMongoURL = new ConfigDefault<>("", "database.mongoURL", AntiVPN.getInstance()), defaultMongoURL = new ConfigDefault<>("", "database.mongoURL", AntiVPN.getInstance()),
defaultUsername = new ConfigDefault<>("root", defaultUsername = new ConfigDefault<>("root", "database.username", AntiVPN.getInstance()),
"database.username", AntiVPN.getInstance()), defaultPassword = new ConfigDefault<>("password", "database.password", AntiVPN.getInstance()),
defaultPassword = new ConfigDefault<>("password", defaultCountryKickReason =
"database.password", AntiVPN.getInstance()), new ConfigDefault<>(
defaultCountryKickReason = new ConfigDefault<>(
"&cSorry, but our server does not allow connections from\n&f%country%", "&cSorry, but our server does not allow connections from\n&f%country%",
"countries.vanillaKickReason", AntiVPN.getInstance()), "countries.vanillaKickReason", AntiVPN.getInstance()),
defaultIp = new ConfigDefault<>("localhost", "database.ip", AntiVPN.getInstance()), defaultIp = new ConfigDefault<>("localhost", "database.ip", AntiVPN.getInstance()),
defaultAlertMsg = new ConfigDefault<>("&8[&6KauriVPN&8] &e%player% &7has joined on a VPN/proxy" + defaultAlertMsg =
" &8(&f%reason%&8) &7in location &8(&f%city%&7, &f%country%&8)", "alerts.message", new ConfigDefault<>(
AntiVPN.getInstance()); "&8[&6KauriVPN&8] &e%player% &7has joined on a VPN/proxy"
private final ConfigDefault<Boolean> cacheResultsDefault = new ConfigDefault<>(true, + " &8(&f%reason%&8) &7in location &8(&f%city%&7, &f%country%&8)",
"cachedResults", AntiVPN.getInstance()), "alerts.message", AntiVPN.getInstance());
defaultUseCredentials = new ConfigDefault<>(true, private final ConfigDefault<Boolean>
"database.useCredentials", AntiVPN.getInstance()), cacheResultsDefault = new ConfigDefault<>(true, "cachedResults", AntiVPN.getInstance()),
defaultDatabaseEnabled = new ConfigDefault<>(false, "database.enabled", defaultUseCredentials =
AntiVPN.getInstance()), defaultCommandsEnable = new ConfigDefault<>(false, new ConfigDefault<>(true, "database.useCredentials", AntiVPN.getInstance()),
"commands.enabled", AntiVPN.getInstance()), defaultKickPlayers defaultDatabaseEnabled =
= new ConfigDefault<>(true, "kickPlayers", AntiVPN.getInstance()), new ConfigDefault<>(false, "database.enabled", AntiVPN.getInstance()),
defaultAlertToStaff = new ConfigDefault<>(true, "alerts.enabled", defaultCommandsEnable = new ConfigDefault<>(false, "commands.enabled", AntiVPN.getInstance()),
AntiVPN.getInstance()), defaultKickPlayers = new ConfigDefault<>(true, "kickPlayers", AntiVPN.getInstance()),
defaultWhitelistCountries = new ConfigDefault<>(true, "countries.whitelist", defaultAlertToStaff = new ConfigDefault<>(true, "alerts.enabled", AntiVPN.getInstance()),
AntiVPN.getInstance()), defaultWhitelistCountries =
new ConfigDefault<>(true, "countries.whitelist", AntiVPN.getInstance()),
defaultMetrics = new ConfigDefault<>(true, "bstats", AntiVPN.getInstance()); defaultMetrics = new ConfigDefault<>(true, "bstats", AntiVPN.getInstance());
private final ConfigDefault<Integer> private final ConfigDefault<Integer> defaultPort =
defaultPort = new ConfigDefault<>(-1, "database.port", AntiVPN.getInstance()); new ConfigDefault<>(-1, "database.port", AntiVPN.getInstance());
private final ConfigDefault<List<String>> prefixWhitelistsDefault = new ConfigDefault<>(new ArrayList<>(), private final ConfigDefault<List<String>>
"prefixWhitelists", AntiVPN.getInstance()), defaultCommands = new ConfigDefault<>( prefixWhitelistsDefault =
Collections.singletonList("kick %player% VPNs are not allowed on our server!"), "commands.execute", new ConfigDefault<>(new ArrayList<>(), "prefixWhitelists", AntiVPN.getInstance()),
defaultCommands =
new ConfigDefault<>(
Collections.singletonList("kick %player% VPNs are not allowed on our server!"),
"commands.execute",
AntiVPN.getInstance()), AntiVPN.getInstance()),
defCountryKickCommands = new ConfigDefault<>(Collections.emptyList(), defCountryKickCommands =
"countries.commands", AntiVPN.getInstance()), new ConfigDefault<>(Collections.emptyList(), "countries.commands", AntiVPN.getInstance()),
defCountrylist = new ConfigDefault<>(new ArrayList<>(), "countries.list", defCountrylist =
AntiVPN.getInstance()); new ConfigDefault<>(new ArrayList<>(), "countries.list", AntiVPN.getInstance());
@Getter @Getter private String license;
private String license; @Getter private String kickMessage;
@Getter @Getter private String databaseType;
private String kickMessage; @Getter private String databaseName;
@Getter
private String databaseType;
@Getter
private String databaseName;
private String mongoURL; private String mongoURL;
@Getter @Getter private String username;
private String username; @Getter private String password;
@Getter @Getter private String ip;
private String password; @Getter private String alertMsg;
@Getter @Getter private String countryVanillaKickReason;
private String ip; @Getter private List<String> prefixWhitelists;
@Getter
private String alertMsg;
@Getter
private String countryVanillaKickReason;
@Getter
private List<String> prefixWhitelists;
private List<String> commands; private List<String> commands;
@Getter @Getter private List<String> countryList;
private List<String> countryList;
private List<String> countryKickCommands; private List<String> countryKickCommands;
private int port; private int port;
private boolean cacheResults; private boolean cacheResults;
@Getter @Getter private boolean databaseEnabled;
private boolean databaseEnabled;
private boolean useCredentials; private boolean useCredentials;
@Getter @Getter private boolean commandsEnabled;
private boolean commandsEnabled; @Getter private boolean kickPlayers;
@Getter
private boolean kickPlayers;
private boolean alertToStaff; private boolean alertToStaff;
private boolean metrics; private boolean metrics;
private boolean whitelistCountries; 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() {
@@ -117,6 +107,7 @@ public class VPNConfig {
/** /**
* If true, staff will be alerted on proxy detection. * If true, staff will be alerted on proxy detection.
*
* @return boolean * @return boolean
*/ */
public boolean isAlertToSTaff() { public boolean isAlertToSTaff() {
@@ -125,6 +116,7 @@ public class VPNConfig {
/** /**
* Commands to run on proxy detection. * Commands to run on proxy detection.
*
* @return List * @return List
*/ */
public List<String> commands() { public List<String> commands() {
@@ -133,6 +125,7 @@ public class VPNConfig {
/** /**
* Whether or not the database we want to connect to requires credentials. * Whether or not the database we want to connect to requires credentials.
*
* @return boolean * @return boolean
*/ */
public boolean useDatabaseCreds() { public boolean useDatabaseCreds() {
@@ -141,6 +134,7 @@ public class VPNConfig {
/** /**
* Only for Mongo only. URL used for connecting to database. Overrides other fields * Only for Mongo only. URL used for connecting to database. Overrides other fields
*
* @return String * @return String
*/ */
public String mongoDatabaseURL() { public String mongoDatabaseURL() {
@@ -148,7 +142,9 @@ public class VPNConfig {
} }
/** /**
* If true, we only allow the {@link VPNConfig#countryKickCommands()}. If false, we blacklist them. * If true, we only allow the {@link VPNConfig#countryKickCommands()}. If false, we blacklist
* them.
*
* @return boolean * @return boolean
*/ */
public boolean getWhitelistCountries() { public boolean getWhitelistCountries() {
@@ -157,6 +153,7 @@ public class VPNConfig {
/** /**
* Returns our configured commands to run on player country detection. * Returns our configured commands to run on player country detection.
*
* @return List * @return List
*/ */
public List<String> countryKickCommands() { public List<String> countryKickCommands() {
@@ -166,10 +163,11 @@ public class VPNConfig {
/** /**
* Gets the port based on configuration. If {@link VPNConfig#port} is -1, will get default port * Gets the port based on configuration. If {@link VPNConfig#port} is -1, will get default port
* based on {@link VPNConfig#getDatabaseType()} lowerCase(). * based on {@link VPNConfig#getDatabaseType()} lowerCase().
*
* @return int * @return int
*/ */
public int getPort() { public int getPort() {
if(port == -1) { if (port == -1) {
switch (getDatabaseType().toLowerCase()) { switch (getDatabaseType().toLowerCase()) {
case "mongodb": case "mongodb":
case "mongo": case "mongo":
@@ -184,18 +182,16 @@ public class VPNConfig {
return port; return port;
} }
/** /**
* If true, <a href="https://bstats.org">...</a> metrics will be collected to improve KauriVPN. * If true, <a href="https://bstats.org">...</a> metrics will be collected to improve KauriVPN.
*
* @return boolean * @return boolean
*/ */
public boolean metrics() { public boolean metrics() {
return metrics; return metrics;
} }
/** /** Grabs all information from the config.yml */
* Grabs all information from the config.yml
*/
public void update() { public void update() {
license = licenseDefault.get(); license = licenseDefault.get();
kickMessage = kickStringDefault.get(); kickMessage = kickStringDefault.get();
@@ -221,5 +217,4 @@ public class VPNConfig {
countryKickCommands = defCountryKickCommands.get(); countryKickCommands = defCountryKickCommands.get();
countryVanillaKickReason = defaultCountryKickReason.get(); countryVanillaKickReason = defaultCountryKickReason.get();
} }
} }
@@ -25,13 +25,12 @@ 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 {
@@ -42,7 +41,6 @@ public abstract class VPNExecutor {
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 registerListeners();
public abstract void log(Level level, String log, Object... objects); public abstract void log(Level level, String log, Object... objects);
@@ -58,53 +56,74 @@ public abstract class VPNExecutor {
} }
public void startKickChecks() { public void startKickChecks() {
kickTask = threadExecutor.scheduleAtFixedRate(() -> { kickTask =
threadExecutor.scheduleAtFixedRate(
() -> {
synchronized (toKick) { synchronized (toKick) {
if(toKick.isEmpty()) return; 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 = AntiVPN.getInstance().getPlayerExecutor().getPlayer(toCheck.second()); Optional<APIPlayer> player =
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) { public void handleKickingOfPlayer(CheckResult result, APIPlayer player) {
//Ensuring kick task is always running // Ensuring kick task is always running
if(kickTask == null || kickTask.isDone() || kickTask.isCancelled()) { if (kickTask == null || kickTask.isDone() || kickTask.isCancelled()) {
startKickChecks(); startKickChecks();
} }
if (AntiVPN.getInstance().getVpnConfig().isAlertToSTaff()) AntiVPN.getInstance().getPlayerExecutor() if (AntiVPN.getInstance().getVpnConfig().isAlertToSTaff())
.getOnlinePlayers() AntiVPN.getInstance().getPlayerExecutor().getOnlinePlayers().stream()
.stream()
.filter(APIPlayer::isAlertsEnabled) .filter(APIPlayer::isAlertsEnabled)
.forEach(pl -> .forEach(
pl.sendMessage(StringUtil.translateAlternateColorCodes('&', pl ->
StringUtil.varReplace(dev.brighten.antivpn.AntiVPN.getInstance().getVpnConfig() pl.sendMessage(
.getAlertMsg(), player, result.response())))); StringUtil.translateAlternateColorCodes(
'&',
StringUtil.varReplace(
dev.brighten.antivpn.AntiVPN.getInstance()
.getVpnConfig()
.getAlertMsg(),
player,
result.response()))));
if(AntiVPN.getInstance().getVpnConfig().isKickPlayers()) { if (AntiVPN.getInstance().getVpnConfig().isKickPlayers()) {
switch (result.resultType()) { switch (result.resultType()) {
case DENIED_PROXY -> player.kickPlayer(StringUtil.varReplace(AntiVPN.getInstance().getVpnConfig() case DENIED_PROXY ->
.getKickMessage(), player, result.response())); player.kickPlayer(
case DENIED_COUNTRY -> player.kickPlayer(StringUtil.varReplace(AntiVPN.getInstance().getVpnConfig() StringUtil.varReplace(
.getCountryVanillaKickReason(), player, result.response())); AntiVPN.getInstance().getVpnConfig().getKickMessage(),
player,
result.response()));
case DENIED_COUNTRY ->
player.kickPlayer(
StringUtil.varReplace(
AntiVPN.getInstance().getVpnConfig().getCountryVanillaKickReason(),
player,
result.response()));
} }
} else { } else {
if(!AntiVPN.getInstance().getVpnConfig().isCommandsEnabled()) return; if (!AntiVPN.getInstance().getVpnConfig().isCommandsEnabled()) return;
} }
Runnable runCommands = () -> { Runnable runCommands =
() -> {
switch (result.resultType()) { switch (result.resultType()) {
case DENIED_PROXY -> { case DENIED_PROXY -> {
for (String command : AntiVPN.getInstance().getVpnConfig().commands()) { for (String command : AntiVPN.getInstance().getVpnConfig().commands()) {
@@ -119,26 +138,33 @@ public abstract class VPNExecutor {
} }
}; };
// Fixes the commands running too fast and causing messaging errors by any downstream plugins like LiteBans // Fixes the commands running too fast and causing messaging errors by any downstream plugins
var scheduleResult = threadExecutor.schedule(runCommands, 1, TimeUnit.SECONDS); // like LiteBans
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. var toAdd = new Tuple<>(result, player.getUuid());
toKick.add(new Tuple<>(result, player.getUuid())); // Ensuring players are actually kicked as they are supposed to be.
threadExecutor.schedule(
() -> {
toKick.add(toAdd);
},
500,
TimeUnit.MILLISECONDS);
} }
public boolean isWhitelisted(UUID uuid) { public boolean isWhitelisted(UUID uuid) {
if(AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled()) { if (AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled()) {
return AntiVPN.getInstance().getDatabase().isWhitelisted(uuid); return AntiVPN.getInstance().getDatabase().isWhitelisted(uuid);
} }
return whitelisted.contains(uuid); return whitelisted.contains(uuid);
} }
public boolean isWhitelisted(String cidr) { public boolean isWhitelisted(String cidr) {
if(AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled()) { if (AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled()) {
return AntiVPN.getInstance().getDatabase().isWhitelisted(cidr); return AntiVPN.getInstance().getDatabase().isWhitelisted(cidr);
} }
try { try {
@@ -148,28 +174,28 @@ public abstract class VPNExecutor {
} }
} }
private final Cache<String, VPNResponse> cachedResponses = Caffeine.newBuilder() private final Cache<String, VPNResponse> cachedResponses =
.expireAfterWrite(20, TimeUnit.MINUTES) Caffeine.newBuilder().expireAfterWrite(20, TimeUnit.MINUTES).maximumSize(4000).build();
.maximumSize(4000)
.build();
public CompletableFuture<VPNResponse> checkIp(String ip) { public CompletableFuture<VPNResponse> checkIp(String ip) {
VPNResponse cached = cachedResponses.getIfPresent(ip); VPNResponse cached = cachedResponses.getIfPresent(ip);
if(cached != null) { if (cached != null) {
return CompletableFuture.completedFuture(cached); return CompletableFuture.completedFuture(cached);
} }
return CompletableFuture.supplyAsync(() -> { return CompletableFuture.supplyAsync(
Optional<VPNResponse> cachedRes = AntiVPN.getInstance().getDatabase().getStoredResponse(ip); () -> {
Optional<VPNResponse> cachedRes =
AntiVPN.getInstance().getDatabase().getStoredResponse(ip);
if(cachedRes.isPresent()) { if (cachedRes.isPresent()) {
return cachedRes.get(); return cachedRes.get();
} } else {
else {
try { try {
VPNResponse response = FunkemunkyAPI VPNResponse response =
.getVPNResponse(ip, AntiVPN.getInstance().getVpnConfig().getLicense(), true); FunkemunkyAPI.getVPNResponse(
ip, AntiVPN.getInstance().getVpnConfig().getLicense(), true);
if (response.isSuccess()) { if (response.isSuccess()) {
AntiVPN.getInstance().getDatabase().cacheResponse(response); AntiVPN.getInstance().getDatabase().cacheResponse(response);
@@ -183,7 +209,8 @@ public abstract class VPNExecutor {
return VPNResponse.FAILED_RESPONSE; return VPNResponse.FAILED_RESPONSE;
} }
} }
}, threadExecutor); },
threadExecutor);
} }
public abstract void disablePlugin(); public abstract void disablePlugin();
@@ -17,14 +17,15 @@
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,7 +21,6 @@ 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;
@@ -65,15 +64,20 @@ public class AlertsCommand extends Command {
@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()) return AntiVPN.getInstance().getMessageHandler() if (!pgetter.isPresent())
.getString("command-misc-playerRequired").getMessage(); return AntiVPN.getInstance()
.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().getMessageHandler().getString("command-alerts-toggled") return AntiVPN.getInstance()
.getMessageHandler()
.getString("command-alerts-toggled")
.getFormattedMessage(new VpnString.Var<>("state", player.isAlertsEnabled())); .getFormattedMessage(new VpnString.Var<>("state", player.isAlertsEnabled()));
} }
@@ -22,7 +22,6 @@ 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;
@@ -68,11 +67,12 @@ public class AllowlistCommand extends Command {
@Override @Override
public String execute(CommandExecutor executor, String[] args) { public String execute(CommandExecutor executor, String[] args) {
if(args.length == 0 || Arrays.stream(secondArgs).noneMatch(arg -> arg.equalsIgnoreCase(args[0]))) { if (args.length == 0
|| Arrays.stream(secondArgs).noneMatch(arg -> arg.equalsIgnoreCase(args[0]))) {
return "&cUsage: /antivpn allowlist " + usage(); return "&cUsage: /antivpn allowlist " + usage();
} }
if(args[0].equalsIgnoreCase("show")) { if (args[0].equalsIgnoreCase("show")) {
// args[1] = optional page number (defaults to 1) // args[1] = optional page number (defaults to 1)
int page = 1; int page = 1;
if (args.length > 1) { if (args.length > 1) {
@@ -86,10 +86,12 @@ public class AllowlistCommand extends Command {
boolean databaseEnabled = AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled(); boolean databaseEnabled = AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled();
List<UUID> uuids = databaseEnabled List<UUID> uuids =
databaseEnabled
? AntiVPN.getInstance().getDatabase().getAllWhitelisted() ? AntiVPN.getInstance().getDatabase().getAllWhitelisted()
: new ArrayList<>(AntiVPN.getInstance().getExecutor().getWhitelisted()); : new ArrayList<>(AntiVPN.getInstance().getExecutor().getWhitelisted());
List<CIDRUtils> ips = databaseEnabled List<CIDRUtils> ips =
databaseEnabled
? AntiVPN.getInstance().getDatabase().getAllWhitelistedIps() ? AntiVPN.getInstance().getDatabase().getAllWhitelistedIps()
: new ArrayList<>(AntiVPN.getInstance().getExecutor().getWhitelistedIps()); : new ArrayList<>(AntiVPN.getInstance().getExecutor().getWhitelistedIps());
@@ -104,7 +106,7 @@ public class AllowlistCommand extends Command {
return buildPage(entries, page, null, "show"); return buildPage(entries, page, null, "show");
} }
if(args[0].equalsIgnoreCase("search")) { if (args[0].equalsIgnoreCase("search")) {
// args[1..n-1] = query terms; args[n] = optional page number if last arg is an integer // args[1..n-1] = query terms; args[n] = optional page number if last arg is an integer
if (args.length < 2) { if (args.length < 2) {
return "&cUsage: /antivpn allowlist search <query> [page]"; return "&cUsage: /antivpn allowlist search <query> [page]";
@@ -119,7 +121,8 @@ public class AllowlistCommand extends Command {
page = candidate; page = candidate;
queryEnd = args.length - 1; queryEnd = args.length - 1;
} }
} catch (NumberFormatException ignored) {} } catch (NumberFormatException ignored) {
}
String search = String.join(" ", Arrays.copyOfRange(args, 1, queryEnd)).toLowerCase(); String search = String.join(" ", Arrays.copyOfRange(args, 1, queryEnd)).toLowerCase();
// Strip color code characters to prevent formatting injection in output // Strip color code characters to prevent formatting injection in output
@@ -131,10 +134,12 @@ public class AllowlistCommand extends Command {
boolean databaseEnabled = AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled(); boolean databaseEnabled = AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled();
List<UUID> uuids = databaseEnabled List<UUID> uuids =
databaseEnabled
? AntiVPN.getInstance().getDatabase().getAllWhitelisted() ? AntiVPN.getInstance().getDatabase().getAllWhitelisted()
: new ArrayList<>(AntiVPN.getInstance().getExecutor().getWhitelisted()); : new ArrayList<>(AntiVPN.getInstance().getExecutor().getWhitelisted());
List<CIDRUtils> ips = databaseEnabled List<CIDRUtils> ips =
databaseEnabled
? AntiVPN.getInstance().getDatabase().getAllWhitelistedIps() ? AntiVPN.getInstance().getDatabase().getAllWhitelistedIps()
: new ArrayList<>(AntiVPN.getInstance().getExecutor().getWhitelistedIps()); : new ArrayList<>(AntiVPN.getInstance().getExecutor().getWhitelistedIps());
@@ -155,24 +160,25 @@ public class AllowlistCommand extends Command {
return buildPage(entries, page, safeSearch, "search " + safeSearch); return buildPage(entries, page, safeSearch, "search " + safeSearch);
} }
if(args.length == 1) if (args.length == 1) return "&cYou have to provide a player to allow or deny exemption.";
return "&cYou have to provide a player to allow or deny exemption.";
boolean databaseEnabled = AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled(); boolean databaseEnabled = AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled();
if(!databaseEnabled) executor.sendMessage("&cThe database is currently not setup, " + if (!databaseEnabled)
"so any changes here will disappear after a restart."); executor.sendMessage(
"&cThe database is currently not setup, "
+ "so any changes here will disappear after a restart.");
CIDRUtils cidrUtils; CIDRUtils cidrUtils;
try { try {
cidrUtils = new CIDRUtils(args[1]); cidrUtils = new CIDRUtils(args[1]);
} catch(IllegalArgumentException | UnknownHostException e) { } catch (IllegalArgumentException | UnknownHostException e) {
cidrUtils = null; cidrUtils = null;
} }
if(cidrUtils != null) { if (cidrUtils != null) {
if(!databaseEnabled) { if (!databaseEnabled) {
return switch (args[0].toLowerCase()) { return switch (args[0].toLowerCase()) {
case "add", "insert" -> { case "add", "insert" -> {
AntiVPN.getInstance().getExecutor().getWhitelistedIps().add(cidrUtils); AntiVPN.getInstance().getExecutor().getWhitelistedIps().add(cidrUtils);
@@ -180,11 +186,13 @@ public class AllowlistCommand extends Command {
} }
case "remove", "delete" -> { case "remove", "delete" -> {
AntiVPN.getInstance().getExecutor().getWhitelistedIps().remove(cidrUtils); AntiVPN.getInstance().getExecutor().getWhitelistedIps().remove(cidrUtils);
yield String.format("&cRemoved &%s &cfrom the exemption allowlist.", cidrUtils.getCidr()); yield String.format(
"&cRemoved &%s &cfrom the exemption allowlist.", cidrUtils.getCidr());
} }
default -> "&c\"" + args[0] + "\" is not a valid argument"; default -> "&c\"" + args[0] + "\" is not a valid argument";
}; };
} else return switch (args[0].toLowerCase()) { } else
return switch (args[0].toLowerCase()) {
case "add", "insert" -> { case "add", "insert" -> {
AntiVPN.getInstance().getExecutor().getWhitelistedIps().add(cidrUtils); AntiVPN.getInstance().getExecutor().getWhitelistedIps().add(cidrUtils);
AntiVPN.getInstance().getDatabase().addWhitelist(cidrUtils); AntiVPN.getInstance().getDatabase().addWhitelist(cidrUtils);
@@ -193,29 +201,39 @@ public class AllowlistCommand extends Command {
case "remove", "delete" -> { case "remove", "delete" -> {
AntiVPN.getInstance().getExecutor().getWhitelistedIps().remove(cidrUtils); AntiVPN.getInstance().getExecutor().getWhitelistedIps().remove(cidrUtils);
AntiVPN.getInstance().getDatabase().removeWhitelist(cidrUtils); AntiVPN.getInstance().getDatabase().removeWhitelist(cidrUtils);
yield String.format("&cRemoved &6%s &cfrom the exemption allowlist.", cidrUtils.getCidr()); yield String.format(
"&cRemoved &6%s &cfrom the exemption allowlist.", cidrUtils.getCidr());
} }
default -> "&c\"" + args[0] + "\" is not a valid argument"; default -> "&c\"" + args[0] + "\" is not a valid argument";
}; };
} }
if(MiscUtils.isIpv4(args[1])) { if (MiscUtils.isIpv4(args[1])) {
if(!databaseEnabled) { if (!databaseEnabled) {
try { try {
return switch(args[0].toLowerCase()) { return switch (args[0].toLowerCase()) {
case "add", "insert" -> { case "add", "insert" -> {
AntiVPN.getInstance().getExecutor().getWhitelistedIps().add(new CIDRUtils(args[1] + "/32")); AntiVPN.getInstance()
.getExecutor()
.getWhitelistedIps()
.add(new CIDRUtils(args[1] + "/32"));
AntiVPN.getInstance().getDatabase().addWhitelist(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"); yield String.format("&aAdded &6%s &ato the exemption allowlist.", args[1] + "/32");
} }
case "remove", "delete" -> { case "remove", "delete" -> {
AntiVPN.getInstance().getExecutor().getWhitelistedIps().remove(new CIDRUtils(args[1] + "/32")); AntiVPN.getInstance()
.getExecutor()
.getWhitelistedIps()
.remove(new CIDRUtils(args[1] + "/32"));
AntiVPN.getInstance().getDatabase().removeWhitelist(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"); yield String.format(
"&cRemoved &6%s &cfrom the exemption allowlist.", args[1] + "/32");
} }
default -> "&c\"" + args[0] + "\" is not a valid argument"; default -> "&c\"" + args[0] + "\" is not a valid argument";
}; };
} catch (UnknownHostException e) { } catch (UnknownHostException e) {
AntiVPN.getInstance().getExecutor().logException("Invalid IP format for allowlist command", e); AntiVPN.getInstance()
.getExecutor()
.logException("Invalid IP format for allowlist command", e);
return "&cInvalid IP format for allowlist command"; return "&cInvalid IP format for allowlist command";
} }
} else { } else {
@@ -227,12 +245,15 @@ public class AllowlistCommand extends Command {
} }
case "remove", "delete" -> { case "remove", "delete" -> {
AntiVPN.getInstance().getDatabase().removeWhitelist(new CIDRUtils(args[1] + "/32")); AntiVPN.getInstance().getDatabase().removeWhitelist(new CIDRUtils(args[1] + "/32"));
yield String.format("&cRemoved &6%s &c from the exemption allowlist.", 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"; default -> "&c\"" + args[0] + "\" is not a valid argument";
}; };
} catch (UnknownHostException e) { } catch (UnknownHostException e) {
AntiVPN.getInstance().getExecutor().logException("Invalid IP format for allowlist command", e); AntiVPN.getInstance()
.getExecutor()
.logException("Invalid IP format for allowlist command", e);
return "&cInvalid IP format for allowlist command"; return "&cInvalid IP format for allowlist command";
} }
} }
@@ -240,19 +261,21 @@ public class AllowlistCommand extends Command {
UUID uuid; UUID uuid;
try { try {
uuid = UUID.fromString(args[1]); uuid = UUID.fromString(args[1]);
} catch(IllegalArgumentException e) { } catch (IllegalArgumentException e) {
Optional<APIPlayer> player = AntiVPN.getInstance().getPlayerExecutor().getPlayer(args[1]); Optional<APIPlayer> player = AntiVPN.getInstance().getPlayerExecutor().getPlayer(args[1]);
if (player.isPresent()) { if (player.isPresent()) {
uuid = player.get().getUuid(); uuid = player.get().getUuid();
} else { } else {
uuid = MiscUtils.lookupUUID(args[1]); uuid = MiscUtils.lookupUUID(args[1]);
if (uuid == null) { if (uuid == null) {
return "&cCould not find a UUID for \"" + args[1] + "\". They might not have provided a valid username."; 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);
@@ -260,7 +283,8 @@ public class AllowlistCommand extends Command {
} }
case "remove", "delete" -> { case "remove", "delete" -> {
AntiVPN.getInstance().getExecutor().getWhitelisted().remove(uuid); AntiVPN.getInstance().getExecutor().getWhitelisted().remove(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";
}; };
@@ -272,7 +296,8 @@ public class AllowlistCommand extends Command {
} }
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";
}; };
@@ -283,7 +308,8 @@ public class AllowlistCommand extends Command {
@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 -> Arrays.stream(secondArgs) case 1 ->
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 -> {
@@ -299,19 +325,26 @@ public class AllowlistCommand extends Command {
}; };
} }
private String buildPage(List<String> entries, int page, String safeSearch, String subcommandPrefix) { private String buildPage(
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("&6&lAllowlist Entries &8(&7Page &f" + page + "&7/&f" + totalPages + "&8)" messages.add(
"&6&lAllowlist Entries &8(&7Page &f"
+ page
+ "&7/&f"
+ totalPages
+ "&8)"
+ (safeSearch != null ? " &7(search: &f" + safeSearch + "&7)" : "")); + (safeSearch != null ? " &7(search: &f" + safeSearch + "&7)" : ""));
messages.add(""); messages.add("");
if (entries.isEmpty()) { if (entries.isEmpty()) {
messages.add(safeSearch != null messages.add(
safeSearch != null
? "&cNo allowlist entries matching &f\"" + safeSearch + "&c\" were found." ? "&cNo allowlist entries matching &f\"" + safeSearch + "&c\" were found."
: "&cThe allowlist is empty."); : "&cThe allowlist is empty.");
} else { } else {
@@ -323,7 +356,8 @@ public class AllowlistCommand extends Command {
if (totalPages > 1) { if (totalPages > 1) {
messages.add(""); messages.add("");
if (page > 1) { if (page > 1) {
messages.add("&7Previous page: &f/antivpn allowlist " + subcommandPrefix + " " + (page - 1)); messages.add(
"&7Previous page: &f/antivpn allowlist " + subcommandPrefix + " " + (page - 1));
} }
if (page < totalPages) { if (page < totalPages) {
messages.add("&7Next page: &f/antivpn allowlist " + subcommandPrefix + " " + (page + 1)); messages.add("&7Next page: &f/antivpn allowlist " + subcommandPrefix + " " + (page + 1));
@@ -20,7 +20,6 @@ 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;
@@ -60,8 +59,14 @@ public class AntiVPNCommand extends Command {
@Override @Override
public Command[] children() { public Command[] children() {
return new Command[] {new LookupCommand(), new AllowlistCommand(), new AlertsCommand(), return new Command[] {
new ClearCacheCommand(), new PlanCommand(), new ReloadCommand()}; new LookupCommand(),
new AllowlistCommand(),
new AlertsCommand(),
new ClearCacheCommand(),
new PlanCommand(),
new ReloadCommand()
};
} }
@Override @Override
@@ -72,14 +77,30 @@ public class AntiVPNCommand extends Command {
messages.add("&6&lAntiVPN Help Page"); messages.add("&6&lAntiVPN Help Page");
messages.add(""); messages.add("");
for (Command cmd : AntiVPN.getInstance().getCommands()) { for (Command cmd : AntiVPN.getInstance().getCommands()) {
messages.add(String.format("&8/&f%s &8- &7&o%s", "&7" + cmd.parent() messages.add(
+ (cmd.parent().length() > 0 ? " " : "") + "&f" + cmd.name() + " &7" String.format(
+ cmd.usage(), cmd.description())); "&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()) { for (Command child : children()) {
messages.add(String.format("&8/&f%s &8- &7&o%s", "&7" + child.parent() messages.add(
+ (child.parent().length() > 0 ? " " : "") + "&f" + child.name() + " &7" String.format(
+ child.usage(), child.description())); "&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")); messages.add(StringUtil.line("&8"));
@@ -89,7 +110,7 @@ public class AntiVPNCommand extends Command {
@Override @Override
public List<String> tabComplete(CommandExecutor executor, String alias, String[] args) { public List<String> tabComplete(CommandExecutor executor, String alias, String[] args) {
if(args.length == 1) if (args.length == 1)
return Arrays.stream(children()) return Arrays.stream(children())
.map(Command::name) .map(Command::name)
.filter(name -> name.toLowerCase().startsWith(args[0].toLowerCase())) .filter(name -> name.toLowerCase().startsWith(args[0].toLowerCase()))
@@ -17,10 +17,8 @@
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;
@@ -62,7 +60,10 @@ public class ClearCacheCommand extends Command {
@Override @Override
public String execute(CommandExecutor executor, String[] args) { public String execute(CommandExecutor executor, String[] args) {
AntiVPN.getInstance().getExecutor().getThreadExecutor().execute(() -> AntiVPN.getInstance().getDatabase().clearResponses()); AntiVPN.getInstance()
.getExecutor()
.getThreadExecutor()
.execute(() -> AntiVPN.getInstance().getDatabase().clearResponses());
return "&aCleared all cached API response information!"; return "&aCleared all cached API response information!";
} }
@@ -21,7 +21,6 @@ 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;
@@ -65,46 +64,50 @@ public class LookupCommand extends Command {
@Override @Override
public String execute(CommandExecutor executor, String[] args) { public String execute(CommandExecutor executor, String[] args) {
if(args.length == 0) { if (args.length == 0) {
return "&cPlease supply a player to check."; return "&cPlease supply a player to check.";
} }
Optional<APIPlayer> player = AntiVPN.getInstance().getPlayerExecutor().getPlayer(args[0]); Optional<APIPlayer> player = AntiVPN.getInstance().getPlayerExecutor().getPlayer(args[0]);
if(player.isEmpty()) { if (player.isEmpty()) {
return String.format("&cNo player found with the name \"%s\"", args[0]); return String.format("&cNo player found with the name \"%s\"", args[0]);
} }
AntiVPN.getInstance().getExecutor() AntiVPN.getInstance()
.getExecutor()
.checkIp(player.get().getIp().getHostAddress()) .checkIp(player.get().getIp().getHostAddress())
.thenAccept(result -> { .thenAccept(
if(!result.isSuccess()) { result -> {
executor.sendMessage("&cThere was an error trying to find the " + if (!result.isSuccess()) {
"information of this player."); executor.sendMessage(
"&cThere was an error trying to find the " + "information of this player.");
return; return;
} }
executor.sendMessage(StringUtil.line("&8")); executor.sendMessage(StringUtil.line("&8"));
executor.sendMessage("&6&l" + player.get().getName() + "&7&l's Connection Information"); executor.sendMessage(
"&6&l" + player.get().getName() + "&7&l's Connection Information");
executor.sendMessage(""); executor.sendMessage("");
executor.sendMessage("&e%s&8: &f%s", "Proxy", result.isProxy() executor.sendMessage(
? "&a" + result.getMethod() : "&cNo"); "&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", "ISP", result.getIsp());
executor.sendMessage("&e%s&8: &f%s", "Country", result.getCountryName()); 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", "City", result.getCity());
executor.sendMessage("&e%s&8: &f%s", "Coordinates", result.getLatitude() executor.sendMessage(
+ "&7/&f" + result.getLongitude()); "&e%s&8: &f%s",
"Coordinates", result.getLatitude() + "&7/&f" + result.getLongitude());
executor.sendMessage(StringUtil.line("&8")); executor.sendMessage(StringUtil.line("&8"));
}); });
return "&7Looking up the IP information for player " + player.get().getName() + "..."; return "&7Looking up the IP information for player " + player.get().getName() + "...";
} }
@Override @Override
public List<String> tabComplete(CommandExecutor executor, String alias, String[] args) { public List<String> tabComplete(CommandExecutor executor, String alias, String[] args) {
if(args.length == 1) return AntiVPN.getInstance().getPlayerExecutor().getOnlinePlayers().stream() if (args.length == 1)
return AntiVPN.getInstance().getPlayerExecutor().getOnlinePlayers().stream()
.map(APIPlayer::getName) .map(APIPlayer::getName)
.filter(name -> name.toLowerCase().startsWith(args[0].toLowerCase())) .filter(name -> name.toLowerCase().startsWith(args[0].toLowerCase()))
.collect(Collectors.toList()); .collect(Collectors.toList());
@@ -17,14 +17,12 @@
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;
@@ -67,17 +65,24 @@ public class PlanCommand extends Command {
@Override @Override
public String execute(CommandExecutor executor, String[] args) { public String execute(CommandExecutor executor, String[] args) {
AntiVPN.getInstance().getExecutor().getThreadExecutor().execute(() -> { AntiVPN.getInstance()
.getExecutor()
.getThreadExecutor()
.execute(
() -> {
QueryResponse result; QueryResponse result;
try { try {
if(AntiVPN.getInstance().getVpnConfig().getLicense().isEmpty()) { if (AntiVPN.getInstance().getVpnConfig().getLicense().isEmpty()) {
result = FunkemunkyAPI.getQueryResponse(); result = FunkemunkyAPI.getQueryResponse();
} else { } else {
result = FunkemunkyAPI.getQueryResponse(AntiVPN.getInstance().getVpnConfig().getLicense()); result =
FunkemunkyAPI.getQueryResponse(
AntiVPN.getInstance().getVpnConfig().getLicense());
if(!result.isValidPlan()) { if (!result.isValidPlan()) {
executor.sendMessage("&cThe license &f%s &cis not a valid license, " + executor.sendMessage(
"checking your Free plan information...", "&cThe license &f%s &cis not a valid license, "
+ "checking your Free plan information...",
AntiVPN.getInstance().getVpnConfig().getLicense()); AntiVPN.getInstance().getVpnConfig().getLicense());
result = FunkemunkyAPI.getQueryResponse(); result = FunkemunkyAPI.getQueryResponse();
@@ -85,26 +90,30 @@ public class PlanCommand extends Command {
} }
String plan = result.getPlanType(); String plan = result.getPlanType();
if(plan.equals("IP")) plan+= " (Free)"; if (plan.equals("IP")) plan += " (Free)";
String queryMax = result.getQueriesMax() == Long.MAX_VALUE String queryMax =
? "Unlimited" : String.valueOf(result.getQueriesMax()); result.getQueriesMax() == Long.MAX_VALUE
? "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("&e%s&8: &f%s&7/&f%s", "Queries Used", executor.sendMessage(
result.getQueries(), queryMax); "&e%s&8: &f%s&7/&f%s", "Queries Used", 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("&cThere was a JSONException thrown while looking up your query " + executor.sendMessage(
"information. Check console for more details."); "&cThere was a JSONException thrown while looking up your query "
+ "information. Check console for more details.");
} catch (IOException e) { } catch (IOException e) {
AntiVPN.getInstance().getExecutor().logException(e); AntiVPN.getInstance().getExecutor().logException(e);
executor.sendMessage("&cThere was a IOException thrown while looking up your query " + executor.sendMessage(
"information. Check console for more details."); "&cThere was a IOException thrown while looking up your query "
+ "information. Check console for more details.");
} }
}); });
return "&7Looking up your query information..."; return "&7Looking up your query information...";
@@ -19,7 +19,6 @@ 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;
@@ -71,7 +70,10 @@ public class ReloadCommand extends Command {
AntiVPN.getInstance().reloadDatabase(); AntiVPN.getInstance().reloadDatabase();
return AntiVPN.getInstance().getMessageHandler().getString("command-reload-complete").getMessage(); return AntiVPN.getInstance()
.getMessageHandler()
.getString("command-reload-complete")
.getMessage();
} }
@Override @Override
@@ -18,7 +18,6 @@ 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;
@@ -24,8 +24,6 @@ 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;
@@ -35,46 +33,71 @@ 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() { public H2VPN() {
AntiVPN.getInstance().getExecutor().getThreadExecutor().scheduleAtFixedRate(() -> { AntiVPN.getInstance()
if(!AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled() || MySQL.isClosed()) return; .getExecutor()
.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().getExecutor().getWhitelisted() AntiVPN.getInstance()
.getExecutor()
.getWhitelisted()
.addAll(AntiVPN.getInstance().getDatabase().getAllWhitelisted()); .addAll(AntiVPN.getInstance().getDatabase().getAllWhitelisted());
//Refreshing whitlisted IPs // Refreshing whitlisted IPs
AntiVPN.getInstance().getExecutor().getWhitelistedIps().clear(); AntiVPN.getInstance().getExecutor().getWhitelistedIps().clear();
AntiVPN.getInstance().getExecutor().getWhitelistedIps() AntiVPN.getInstance()
.getExecutor()
.getWhitelistedIps()
.addAll(AntiVPN.getInstance().getDatabase().getAllWhitelistedIps()); .addAll(AntiVPN.getInstance().getDatabase().getAllWhitelistedIps());
}, 2, 30, TimeUnit.SECONDS); },
2,
30,
TimeUnit.SECONDS);
} }
@Override @Override
public Optional<VPNResponse> getStoredResponse(String ip) { public Optional<VPNResponse> getStoredResponse(String ip) {
if (!AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled()|| MySQL.isClosed()) if (!AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled() || MySQL.isClosed())
return Optional.empty(); return Optional.empty();
try(ExecutableStatement statement = Query.prepare("select * from `responses` where `ip` = ? limit 1").append(ip)) { try (ExecutableStatement statement =
try(ResultSet rs = statement.executeQuery()) { Query.prepare("select * from `responses` where `ip` = ? limit 1").append(ip)) {
try (ResultSet rs = statement.executeQuery()) {
if (rs != null && rs.next()) { if (rs != null && rs.next()) {
return Optional.of(new VPNResponse(rs.getString("asn"), rs.getString("ip"), return Optional.of(
rs.getString("countryName"), rs.getString("countryCode"), new VPNResponse(
rs.getString("city"), rs.getString("timeZone"), rs.getString("asn"),
rs.getString("method"), rs.getString("isp"), "N/A", rs.getString("ip"),
rs.getBoolean("proxy"), rs.getBoolean("cached"), true, rs.getString("countryName"),
rs.getDouble("latitude"), rs.getDouble("longitude"), rs.getString("countryCode"),
rs.getTimestamp("inserted").getTime(), -1)); 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) { } catch (SQLException e) {
AntiVPN.getInstance().getExecutor().logException("There was a problem getting a response for " AntiVPN.getInstance()
+ ip, e); .getExecutor()
.logException("There was a problem getting a response for " + ip, e);
} catch (Exception e) { } catch (Exception e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
@@ -92,46 +115,60 @@ public class H2VPN implements VPNDatabase {
*/ */
@Override @Override
public void cacheResponse(VPNResponse toCache) { public void cacheResponse(VPNResponse toCache) {
if (!AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled() || MySQL.isClosed()) if (!AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled() || MySQL.isClosed()) return;
return;
try(var statement = Query.prepare("insert into `responses` (`ip`,`asn`,`countryName`,`countryCode`,`city`,`timeZone`," try (var statement =
Query.prepare(
"insert into `responses` (`ip`,`asn`,`countryName`,`countryCode`,`city`,`timeZone`,"
+ "`method`,`isp`,`proxy`,`cached`,`inserted`,`latitude`,`longitude`) values (?,?,?,?,?,?,?,?,?,?,?,?,?)") + "`method`,`isp`,`proxy`,`cached`,`inserted`,`latitude`,`longitude`) values (?,?,?,?,?,?,?,?,?,?,?,?,?)")
.append(toCache.getIp()).append(toCache.getAsn()).append(toCache.getCountryName()) .append(toCache.getIp())
.append(toCache.getCountryCode()).append(toCache.getCity()).append(toCache.getTimeZone()) .append(toCache.getAsn())
.append(toCache.getMethod()).append(toCache.getIsp()).append(toCache.isProxy()) .append(toCache.getCountryName())
.append(toCache.isCached()).append(new Timestamp(System.currentTimeMillis())) .append(toCache.getCountryCode())
.append(toCache.getLatitude()).append(toCache.getLongitude())) { .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(); statement.execute();
} catch(SQLException e) { } catch (SQLException e) {
AntiVPN.getInstance().getExecutor().logException("Could not cache response for IP: " + toCache.getIp(), e); AntiVPN.getInstance()
.getExecutor()
.logException("Could not cache response for IP: " + toCache.getIp(), e);
} }
} }
@Override @Override
public void deleteResponse(String ip) { public void deleteResponse(String ip) {
if(!AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled() || MySQL.isClosed()) if (!AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled() || MySQL.isClosed()) return;
return;
try(var statement = Query.prepare("delete from `responses` where `ip` = ?").append(ip)) { try (var statement = Query.prepare("delete from `responses` where `ip` = ?").append(ip)) {
statement.execute(); statement.execute();
} catch (SQLException e) { } catch (SQLException e) {
AntiVPN.getInstance().getExecutor().logException("Could not delete response from IP: " + ip, e); AntiVPN.getInstance()
.getExecutor()
.logException("Could not delete response from IP: " + ip, e);
} }
} }
@Override @Override
public boolean isWhitelisted(UUID uuid) { public boolean isWhitelisted(UUID uuid) {
if (!AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled() || MySQL.isClosed()) if (!AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled() || MySQL.isClosed()) return false;
return false;
try(var statement = Query.prepare("select uuid from `whitelisted` where `uuid` = ? limit 1") try (var statement =
Query.prepare("select uuid from `whitelisted` where `uuid` = ? limit 1")
.append(uuid.toString())) { .append(uuid.toString())) {
try(var set = statement.executeQuery()) { try (var set = statement.executeQuery()) {
return set != null && set.next() && set.getString("uuid") != null; return set != null && set.next() && set.getString("uuid") != null;
} }
} catch (SQLException e) { } catch (SQLException e) {
AntiVPN.getInstance().getExecutor().logException("Could not check whitelist for uuid '" + uuid + "' due to SQL error.", e); AntiVPN.getInstance()
.getExecutor()
.logException("Could not check whitelist for uuid '" + uuid + "' due to SQL error.", e);
return false; return false;
} }
} }
@@ -144,73 +181,88 @@ public class H2VPN implements VPNDatabase {
@Override @Override
public boolean isWhitelisted(CIDRUtils cidr) { public boolean isWhitelisted(CIDRUtils cidr) {
if (!AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled() || MySQL.isClosed()) if (!AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled() || MySQL.isClosed()) return false;
return false;
BigInteger start = cidr.getStartIpInt(); BigInteger start = cidr.getStartIpInt();
BigInteger end = cidr.getEndIpInt(); BigInteger end = cidr.getEndIpInt();
try(var statement = Query.prepare("SELECT * FROM `whitelisted-ranges` WHERE ip_start <= ? AND ip_end >= ?") try (var statement =
.append(start).append(end)) { Query.prepare("SELECT * FROM `whitelisted-ranges` WHERE ip_start <= ? AND ip_end >= ?")
.append(start)
.append(end)) {
try(var result = statement.executeQuery()) { try (var result = statement.executeQuery()) {
return result.next(); return result.next();
} }
} catch (SQLException e) { } catch (SQLException e) {
AntiVPN.getInstance().getExecutor().logException("Could not check whitelist for cidr '" + cidr + "' due to SQL error.", e); AntiVPN.getInstance()
.getExecutor()
.logException("Could not check whitelist for cidr '" + cidr + "' due to SQL error.", e);
} }
return false; return false;
} }
@Override @Override
public void addWhitelist(UUID uuid) { public void addWhitelist(UUID uuid) {
if (!AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled() || MySQL.isClosed()) if (!AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled() || MySQL.isClosed()) return;
return;
try(var statement = Query.prepare("insert into `whitelisted` (`uuid`) values (?)").append(uuid.toString())) { try (var statement =
Query.prepare("insert into `whitelisted` (`uuid`) values (?)").append(uuid.toString())) {
statement.execute(); statement.execute();
AntiVPN.getInstance().getExecutor().getWhitelisted().add(uuid); AntiVPN.getInstance().getExecutor().getWhitelisted().add(uuid);
} catch (SQLException e) { } catch (SQLException e) {
AntiVPN.getInstance().getExecutor().logException("Could not add uuid '" + uuid + "' to whitelist due to SQL error.", e); AntiVPN.getInstance()
.getExecutor()
.logException("Could not add uuid '" + uuid + "' to whitelist due to SQL error.", e);
} }
} }
@Override @Override
public void removeWhitelist(UUID uuid) { public void removeWhitelist(UUID uuid) {
if (!AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled() || MySQL.isClosed()) if (!AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled() || MySQL.isClosed()) return;
return; try (var statement =
try(var statement = Query.prepare("delete from `whitelisted` where `uuid` = ?").append(uuid.toString())) { Query.prepare("delete from `whitelisted` where `uuid` = ?").append(uuid.toString())) {
statement.execute(); statement.execute();
AntiVPN.getInstance().getExecutor().getWhitelisted().remove(uuid); AntiVPN.getInstance().getExecutor().getWhitelisted().remove(uuid);
} catch (SQLException e) { } catch (SQLException e) {
AntiVPN.getInstance().getExecutor().logException("Could not remove uuid '" + uuid + "' from whitelist due to SQL error.", e); AntiVPN.getInstance()
.getExecutor()
.logException("Could not remove uuid '" + uuid + "' from whitelist due to SQL error.", e);
} }
} }
@Override @Override
public void addWhitelist(CIDRUtils cidr) { public void addWhitelist(CIDRUtils cidr) {
if (!AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled() || MySQL.isClosed()) if (!AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled() || MySQL.isClosed()) return;
return;
try(var statement = Query.prepare("insert into `whitelisted-ranges` (`cidr_string`, `ip_start`, `ip_end`) values (?, ?, ?)") try (var statement =
.append(cidr.getCidr()).append(cidr.getStartIpInt()).append(cidr.getEndIpInt())) { Query.prepare(
"insert into `whitelisted-ranges` (`cidr_string`, `ip_start`, `ip_end`) values (?, ?, ?)")
.append(cidr.getCidr())
.append(cidr.getStartIpInt())
.append(cidr.getEndIpInt())) {
statement.execute(); statement.execute();
} catch (SQLException e) { } catch (SQLException e) {
AntiVPN.getInstance().getExecutor().logException("Could not add cidr '" + cidr + "' to whitelist due to SQL error.", e); AntiVPN.getInstance()
.getExecutor()
.logException("Could not add cidr '" + cidr + "' to whitelist due to SQL error.", e);
} }
} }
@Override @Override
public void removeWhitelist(CIDRUtils cidr) { public void removeWhitelist(CIDRUtils cidr) {
if (!AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled() || MySQL.isClosed()) if (!AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled() || MySQL.isClosed()) return;
return;
try(var statement = Query.prepare("delete from `whitelisted-ranges` where `cidr_string` = ?").append(cidr.getCidr())) { try (var statement =
Query.prepare("delete from `whitelisted-ranges` where `cidr_string` = ?")
.append(cidr.getCidr())) {
statement.execute(); statement.execute();
} catch (SQLException e) { } catch (SQLException e) {
AntiVPN.getInstance().getExecutor().logException("Could not remove cidr '" + cidr + "' from whitelist due to SQL error.", e); AntiVPN.getInstance()
.getExecutor()
.logException("Could not remove cidr '" + cidr + "' from whitelist due to SQL error.", e);
} }
} }
@@ -218,13 +270,14 @@ public class H2VPN implements VPNDatabase {
public List<UUID> getAllWhitelisted() { public List<UUID> getAllWhitelisted() {
List<UUID> uuids = new ArrayList<>(); List<UUID> uuids = new ArrayList<>();
if (!AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled() || MySQL.isClosed()) if (!AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled() || MySQL.isClosed()) return uuids;
return uuids;
try(var statement = Query.prepare("select uuid from `whitelisted`")) { try (var statement = Query.prepare("select uuid from `whitelisted`")) {
statement.execute(set -> uuids.add(UUID.fromString(set.getString("uuid")))); statement.execute(set -> uuids.add(UUID.fromString(set.getString("uuid"))));
} catch (SQLException e) { } catch (SQLException e) {
AntiVPN.getInstance().getExecutor().logException("Could not get all whitelisted players due to SQL error.", e); AntiVPN.getInstance()
.getExecutor()
.logException("Could not get all whitelisted players due to SQL error.", e);
} }
return uuids; return uuids;
@@ -234,23 +287,27 @@ public class H2VPN implements VPNDatabase {
public List<CIDRUtils> getAllWhitelistedIps() { public List<CIDRUtils> getAllWhitelistedIps() {
List<CIDRUtils> ips = new ArrayList<>(); List<CIDRUtils> ips = new ArrayList<>();
if (!AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled() || MySQL.isClosed()) if (!AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled() || MySQL.isClosed()) return ips;
return ips; try (var statement =
try(var statement = Query.prepare("select `cidr_string`, `ip_start`, `ip_end` from `whitelisted-ranges`")) { Query.prepare("select `cidr_string`, `ip_start`, `ip_end` from `whitelisted-ranges`")) {
statement.execute(set -> { statement.execute(
set -> {
try { try {
String cidrString = set.getString("cidr_string"); String cidrString = set.getString("cidr_string");
ips.add(new CIDRUtils(cidrString)); ips.add(new CIDRUtils(cidrString));
} catch (UnknownHostException e) { } catch (UnknownHostException e) {
AntiVPN.getInstance().getExecutor() AntiVPN.getInstance()
.logException("Could not format ip " .getExecutor()
+ set.getString("cidr_string") + " into a CIDR!", e); .logException(
"Could not format ip " + set.getString("cidr_string") + " into a CIDR!", e);
} }
}); });
} catch (SQLException e) { } catch (SQLException e) {
AntiVPN.getInstance().getExecutor().logException("Could not get all whitelisted ips due to SQL error.", e); AntiVPN.getInstance()
.getExecutor()
.logException("Could not get all whitelisted ips due to SQL error.", e);
} }
return ips; return ips;
@@ -258,16 +315,22 @@ public class H2VPN implements VPNDatabase {
@Override @Override
public void alertsState(UUID uuid, Consumer<Boolean> result) { public void alertsState(UUID uuid, Consumer<Boolean> result) {
if(MySQL.isClosed()) return; if (MySQL.isClosed()) return;
AntiVPN.getInstance().getExecutor().getThreadExecutor().execute(() -> { AntiVPN.getInstance()
.getExecutor()
try(var statement = Query.prepare("select * from `alerts` where `uuid` = ? limit 1") .getThreadExecutor()
.execute(
() -> {
try (var statement =
Query.prepare("select * from `alerts` where `uuid` = ? limit 1")
.append(uuid.toString())) { .append(uuid.toString())) {
try(var set = statement.executeQuery()) { try (var set = statement.executeQuery()) {
result.accept(set != null && set.next() && set.getString("uuid") != null); result.accept(set != null && set.next() && set.getString("uuid") != null);
} }
} catch (SQLException e) { } catch (SQLException e) {
AntiVPN.getInstance().getExecutor().logException("There was a problem getting alerts state for " + uuid, e); AntiVPN.getInstance()
.getExecutor()
.logException("There was a problem getting alerts state for " + uuid, e);
result.accept(false); result.accept(false);
} }
}); });
@@ -275,52 +338,59 @@ public class H2VPN implements VPNDatabase {
@Override @Override
public void updateAlertsState(UUID uuid, boolean enabled) { public void updateAlertsState(UUID uuid, boolean enabled) {
if(MySQL.isClosed()) return; if (MySQL.isClosed()) return;
if(enabled) { if (enabled) {
//We want to make sure there isn't already a uuid inserted to prevent double insertions // 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 alertsState(
if(!alreadyEnabled) { uuid,
try(var statement = Query.prepare("insert into `alerts` (`uuid`) values (?)") 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())) { .append(uuid.toString())) {
statement.execute(); statement.execute();
} catch (SQLException e) { } catch (SQLException e) {
AntiVPN.getInstance().getExecutor() AntiVPN.getInstance()
.getExecutor()
.logException("There was a problem updating alerts state for " + uuid, e); .logException("There was a problem updating alerts state for " + uuid, e);
} }
} //No need to insert again of already enabled } // No need to insert again of already enabled
}); });
//Removing any uuid from the alerts table will disable alerts globally. // Removing any uuid from the alerts table will disable alerts globally.
} else { } else {
try(var statement = Query.prepare("delete from `alerts` where `uuid` = ?").append(uuid.toString())) { try (var statement =
Query.prepare("delete from `alerts` where `uuid` = ?").append(uuid.toString())) {
statement.execute(); statement.execute();
} catch (SQLException e) { } catch (SQLException e) {
AntiVPN.getInstance().getExecutor().logException("There was a problem updating alerts state for " AntiVPN.getInstance()
+ uuid, e); .getExecutor()
.logException("There was a problem updating alerts state for " + uuid, e);
} }
} }
} }
@Override @Override
public void clearResponses() { public void clearResponses() {
if(MySQL.isClosed()) return; if (MySQL.isClosed()) return;
try(var statement = Query.prepare("delete from `responses`")) { try (var statement = Query.prepare("delete from `responses`")) {
statement.execute(); statement.execute();
} catch (SQLException e) { } catch (SQLException e) {
AntiVPN.getInstance().getExecutor().logException("There was a problem clearing responses.", e); AntiVPN.getInstance()
.getExecutor()
.logException("There was a problem clearing responses.", e);
} }
} }
@Override @Override
public void init() { public void init() {
if (!AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled()) if (!AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled()) return;
return;
AntiVPN.getInstance().getExecutor().log("Initializing H2..."); AntiVPN.getInstance().getExecutor().log("Initializing H2...");
MySQL.initH2(); MySQL.initH2();
try { try {
for (Version<H2VPN> version : Version.h2Versions) { for (Version<H2VPN> version : Version.h2Versions) {
if(version.needsUpdate(this)) { if (version.needsUpdate(this)) {
version.update(this); version.update(this);
} }
} }
@@ -330,13 +400,12 @@ public class H2VPN implements VPNDatabase {
AntiVPN.getInstance().getExecutor().log("Creating tables..."); AntiVPN.getInstance().getExecutor().log("Creating tables...");
//Running check for old table types to update // Running check for old table types to update
} }
@Override @Override
public void shutdown() { public void shutdown() {
if (!AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled()) if (!AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled()) return;
return;
MySQL.shutdown(); MySQL.shutdown();
} }
@@ -344,18 +413,21 @@ public class H2VPN implements VPNDatabase {
public void backupDatabase() { public void backupDatabase() {
File dataFolder = new File(AntiVPN.getInstance().getPluginFolder(), "databases"); File dataFolder = new File(AntiVPN.getInstance().getPluginFolder(), "databases");
if(!dataFolder.exists() || MySQL.isClosed()) { if (!dataFolder.exists() || MySQL.isClosed()) {
return; return;
} }
try { try {
var connection = Query.getConn(); var connection = Query.getConn();
if (connection == null || connection.getMetaData() == null if (connection == null
|| connection.getMetaData() == null
|| !connection.getMetaData().getDatabaseProductName().equalsIgnoreCase("H2")) { || !connection.getMetaData().getDatabaseProductName().equalsIgnoreCase("H2")) {
return; return;
} }
} catch (SQLException e) { } catch (SQLException e) {
AntiVPN.getInstance().getExecutor().logException("Could not verify database type before H2 backup.", e); AntiVPN.getInstance()
.getExecutor()
.logException("Could not verify database type before H2 backup.", e);
return; return;
} }
@@ -365,16 +437,17 @@ public class H2VPN implements VPNDatabase {
return; return;
} }
File backupFile = new File(backupDir, "database.h2_backup_" + System.currentTimeMillis() + ".zip"); File backupFile =
String backupPath = backupFile.getAbsolutePath() new File(backupDir, "database.h2_backup_" + System.currentTimeMillis() + ".zip");
.replace("\\", "/") String backupPath = backupFile.getAbsolutePath().replace("\\", "/").replace("'", "''");
.replace("'", "''");
try (var statement = Query.prepare("BACKUP TO '" + backupPath + "'")) { try (var statement = Query.prepare("BACKUP TO '" + backupPath + "'")) {
statement.execute(); statement.execute();
AntiVPN.getInstance().getExecutor().log("Created H2 backup at " + backupFile.getName()); AntiVPN.getInstance().getExecutor().log("Created H2 backup at " + backupFile.getName());
} catch (SQLException e) { } catch (SQLException e) {
AntiVPN.getInstance().getExecutor().logException("Could not create H2 backup before migration.", e); AntiVPN.getInstance()
.getExecutor()
.logException("Could not create H2 backup before migration.", e);
} }
} }
} }
@@ -23,7 +23,6 @@ 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;
@@ -33,23 +32,33 @@ 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 @Override
public void update(VPNDatabase database) throws DatabaseException { public void update(VPNDatabase database) throws DatabaseException {
try { try {
closeOnEnd(Query.prepare("create table if not exists `whitelisted` (`uuid` varchar(36) not null)")) closeOnEnd(
Query.prepare(
"create table if not exists `whitelisted` (`uuid` varchar(36) not null)"))
.execute(); .execute();
closeOnEnd(Query.prepare("create table if not exists `whitelisted-ips` (`ip` varchar(45) not null)")) closeOnEnd(
Query.prepare(
"create table if not exists `whitelisted-ips` (`ip` varchar(45) not null)"))
.execute(); .execute();
closeOnEnd(Query closeOnEnd(
.prepare("create table if not exists `responses` (`ip` varchar(45) not null, `asn` varchar(12)," 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), " + "`countryName` text, `countryCode` varchar(10), `city` text, `timeZone` varchar(64), "
+ "`method` varchar(32), `isp` text, `proxy` boolean, `cached` boolean, `inserted` timestamp," + "`method` varchar(32), `isp` text, `proxy` boolean, `cached` boolean, `inserted` timestamp,"
+ "`latitude` double, `longitude` double)")).execute(); + "`latitude` double, `longitude` double)"))
.execute();
closeOnEnd(Query.prepare("create table if not exists `alerts` (`uuid` varchar(36) not null)")) closeOnEnd(Query.prepare("create table if not exists `alerts` (`uuid` varchar(36) not null)"))
.execute(); .execute();
closeOnEnd(Query.prepare("create table if not exists `database_version` (`version` int)")).execute(); closeOnEnd(Query.prepare("create table if not exists `database_version` (`version` int)"))
closeOnEnd(Query.prepare("insert into `database_version` (`version`) values (?)") .execute();
.append(versionNumber())).execute(); closeOnEnd(
Query.prepare("insert into `database_version` (`version`) values (?)")
.append(versionNumber()))
.execute();
AntiVPN.getInstance().getExecutor().log("Creating indexes..."); AntiVPN.getInstance().getExecutor().log("Creating indexes...");
createIndexIfAbsent("whitelisted", "whitelisted_uuid_1", "`uuid`"); createIndexIfAbsent("whitelisted", "whitelisted_uuid_1", "`uuid`");
@@ -70,17 +79,16 @@ public class First implements Version<VPNDatabase> {
return statement; return statement;
} }
protected void createIndexIfAbsent(String tableName, String indexName, String columnList) throws SQLException { protected void createIndexIfAbsent(String tableName, String indexName, String columnList)
throws SQLException {
if (hasIndex(tableName, indexName)) { if (hasIndex(tableName, indexName)) {
return; return;
} }
closeOnEnd(Query.prepare(String.format( closeOnEnd(
"create index `%s` on `%s` (%s)", Query.prepare(
indexName, String.format("create index `%s` on `%s` (%s)", indexName, tableName, columnList)))
tableName, .execute();
columnList
))).execute();
} }
protected void dropIndexIfPresent(String tableName, String indexName) throws SQLException { protected void dropIndexIfPresent(String tableName, String indexName) throws SQLException {
@@ -88,11 +96,8 @@ public class First implements Version<VPNDatabase> {
return; return;
} }
closeOnEnd(Query.prepare(String.format( closeOnEnd(Query.prepare(String.format("drop index `%s` on `%s`", indexName, tableName)))
"drop index `%s` on `%s`", .execute();
indexName,
tableName
))).execute();
} }
protected boolean hasIndex(String tableName, String indexName) throws SQLException { protected boolean hasIndex(String tableName, String indexName) throws SQLException {
@@ -118,13 +123,12 @@ public class First implements Version<VPNDatabase> {
@Override @Override
public boolean needsUpdate(VPNDatabase database) { public boolean needsUpdate(VPNDatabase database) {
try(var statement = Query.prepare("select * from `database_version` where version = 0")) { try (var statement = Query.prepare("select * from `database_version` where version = 0")) {
try(ResultSet set = statement.executeQuery()) { try (ResultSet set = statement.executeQuery()) {
return !set.next(); return !set.next();
} }
} catch (SQLException e) { } catch (SQLException e) {
return true; return true;
} }
} }
} }
@@ -26,7 +26,6 @@ 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;
@@ -37,13 +36,13 @@ public class Second extends First implements Version<VPNDatabase> {
@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<>(); List<String> whitelistedIps = new ArrayList<>();
try (var statement = Query.prepare("SELECT * FROM `whitelisted-ips`")) { try (var statement = Query.prepare("SELECT * FROM `whitelisted-ips`")) {
try(var set = statement.executeQuery()) { try (var set = statement.executeQuery()) {
while (set.next()) { while (set.next()) {
whitelistedIps.add(set.getString("ip")); whitelistedIps.add(set.getString("ip"));
} }
@@ -53,24 +52,33 @@ public class Second extends First implements Version<VPNDatabase> {
} }
try { try {
closeOnEnd(Query.prepare("CREATE TABLE IF NOT EXISTS `whitelisted-ranges` " + closeOnEnd(
"(id INT AUTO_INCREMENT PRIMARY KEY, " + Query.prepare(
"cidr_string VARCHAR(45), " + "CREATE TABLE IF NOT EXISTS `whitelisted-ranges` "
"ip_start BIGINT NOT NULL, " + + "(id INT AUTO_INCREMENT PRIMARY KEY, "
"ip_end BIGINT NOT NULL)")) + "cidr_string VARCHAR(45), "
+ "ip_start BIGINT NOT NULL, "
+ "ip_end BIGINT NOT NULL)"))
.execute(); .execute();
createIndexIfAbsent("whitelisted-ranges", "idx_ip_range", "ip_start, ip_end"); createIndexIfAbsent("whitelisted-ranges", "idx_ip_range", "ip_start, ip_end");
var cidrs = whitelistedIps.stream().map(ip -> { var cidrs =
whitelistedIps.stream()
.map(
ip -> {
try { try {
return new CIDRUtils(ip + "/32"); return new CIDRUtils(ip + "/32");
} catch (UnknownHostException e) { } catch (UnknownHostException e) {
throw new RuntimeException("Could not format ip " + ip + " into a CIDR!", 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 (?, ?, ?)"); .toList();
var insertStatement =
Query.prepare(
"INSERT INTO `whitelisted-ranges` (`cidr_string`, `ip_start`, `ip_end`) VALUES (?, ?, ?)");
for (CIDRUtils cidr : cidrs) { for (CIDRUtils cidr : cidrs) {
insertStatement = insertStatement insertStatement =
insertStatement
.append(cidr.toString()) .append(cidr.toString())
.append(cidr.getStartIpInt()) .append(cidr.getStartIpInt())
.append(cidr.getEndIpInt()) .append(cidr.getEndIpInt())
@@ -80,17 +88,23 @@ public class Second extends First implements Version<VPNDatabase> {
int[] updateCounts = insertStatement.executeBatch(); int[] updateCounts = insertStatement.executeBatch();
for (int updateCount : updateCounts) { for (int updateCount : updateCounts) {
if(updateCount == 0) { if (updateCount == 0) {
throw new RuntimeException("Could not insert a CIDR from previous whitelisted lists, attempted to restore previous database!"); 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", "ip_1");
dropIndexIfPresent("whitelisted-ips", "whitelisted_ips_ip_1"); dropIndexIfPresent("whitelisted-ips", "whitelisted_ips_ip_1");
closeOnEnd(Query.prepare("DROP TABLE `whitelisted-ips`")).execute(); closeOnEnd(Query.prepare("DROP TABLE `whitelisted-ips`")).execute();
closeOnEnd(Query.prepare("INSERT INTO `database_version` (`version`) VALUES (?)").append(versionNumber())).execute(); closeOnEnd(
Query.prepare("INSERT INTO `database_version` (`version`) VALUES (?)")
.append(versionNumber()))
.execute();
} catch (Throwable e) { } catch (Throwable e) {
AntiVPN.getInstance().getExecutor().log("Failed to update database to version 1: " + e.getMessage()); AntiVPN.getInstance()
.getExecutor()
.log("Failed to update database to version 1: " + e.getMessage());
try { try {
rollback(whitelistedIps); rollback(whitelistedIps);
} catch (SQLException ex) { } catch (SQLException ex) {
@@ -101,7 +115,6 @@ public class Second extends First implements Version<VPNDatabase> {
MiscUtils.close(toClose.toArray(AutoCloseable[]::new)); MiscUtils.close(toClose.toArray(AutoCloseable[]::new));
toClose.clear(); toClose.clear();
} }
} }
private ExecutableStatement closeOnEnd(ExecutableStatement statement) { private ExecutableStatement closeOnEnd(ExecutableStatement statement) {
@@ -112,25 +125,27 @@ public class Second extends First implements Version<VPNDatabase> {
private void rollback(List<String> ipAddresses) throws SQLException { private void rollback(List<String> ipAddresses) throws SQLException {
AntiVPN.getInstance().getExecutor().log("Rolling back to version 0..."); AntiVPN.getInstance().getExecutor().log("Rolling back to version 0...");
dropIndexIfPresent("whitelisted-ranges", "idx_ip_range"); dropIndexIfPresent("whitelisted-ranges", "idx_ip_range");
try(var statement = Query.prepare("DROP TABLE `whitelisted-ranges`")) { try (var statement = Query.prepare("DROP TABLE `whitelisted-ranges`")) {
statement.execute(); statement.execute();
} }
try(var statement = Query.prepare("DELETE FROM `database_version` WHERE version = ?").append(versionNumber())) { try (var statement =
Query.prepare("DELETE FROM `database_version` WHERE version = ?").append(versionNumber())) {
statement.execute(); statement.execute();
} }
try(var statement = Query.prepare("CREATE TABLE IF NOT EXISTS `whitelisted-ips` (`ip` VARCHAR(45) NOT NULL)")) { try (var statement =
Query.prepare("CREATE TABLE IF NOT EXISTS `whitelisted-ips` (`ip` VARCHAR(45) NOT NULL)")) {
statement.execute(); statement.execute();
} }
createIndexIfAbsent("whitelisted-ips", "whitelisted_ips_ip_1", "`ip`"); createIndexIfAbsent("whitelisted-ips", "whitelisted_ips_ip_1", "`ip`");
try(var statement = Query.prepare("DELETE FROM `whitelisted-ips`")) { try (var statement = Query.prepare("DELETE FROM `whitelisted-ips`")) {
statement.execute(); statement.execute();
} }
try(var statement = Query.prepare("INSERT INTO `whitelisted-ips` (`ip`) VALUES (?)")) { try (var statement = Query.prepare("INSERT INTO `whitelisted-ips` (`ip`) VALUES (?)")) {
for (String ip : ipAddresses) { for (String ip : ipAddresses) {
statement.append(ip); statement.append(ip);
statement.addBatch(); statement.addBatch();
@@ -148,7 +163,7 @@ public class Second extends First implements Version<VPNDatabase> {
@Override @Override
public boolean needsUpdate(VPNDatabase database) { public boolean needsUpdate(VPNDatabase database) {
try (var statement = Query.prepare("select * from `database_version` where version = 1")) { try (var statement = Query.prepare("select * from `database_version` where version = 1")) {
try(var set = statement.executeQuery()) { try (var set = statement.executeQuery()) {
return !set.next(); return !set.next();
} }
} catch (SQLException e) { } catch (SQLException e) {
@@ -23,7 +23,6 @@ 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;
@@ -38,62 +37,110 @@ public class Third implements Version<VPNDatabase> {
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(set -> { preparedQuery.execute(
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().getExecutor().log(Level.WARNING, "Found multiple CIDR ranges for whitelist range for %s, %s!", start, end); AntiVPN.getInstance()
.getExecutor()
.log(
Level.WARNING,
"Found multiple CIDR ranges for whitelist range for %s, %s!",
start,
end);
} else ipRanges.addAll(range); } else ipRanges.addAll(range);
} catch (UnknownHostException e) { } catch (UnknownHostException e) {
AntiVPN.getInstance().getExecutor().logException( AntiVPN.getInstance()
.getExecutor()
.logException(
String.format("Could not convert ip range to CIDR! %s, %s", start, end), e); String.format("Could not convert ip range to CIDR! %s, %s", start, end), e);
} }
}); });
} catch (SQLException e) { } catch (SQLException e) {
AntiVPN.getInstance().getExecutor().logException("Could not get all whitelisted ranges due to SQL error.", e); AntiVPN.getInstance()
.getExecutor()
.logException("Could not get all whitelisted ranges due to SQL error.", e);
} }
AntiVPN.getInstance().getExecutor().log("Inserting %s new ranges into database...", rangesToInsert.size()); AntiVPN.getInstance()
.getExecutor()
.log("Inserting %s new ranges into database...", rangesToInsert.size());
for (CIDRUtils cidr : rangesToInsert) { for (CIDRUtils cidr : rangesToInsert) {
try(var statement = Query.prepare("insert into `whitelisted-ranges` (`cidr_string`, `ip_start`, `ip_end`) values (?, ?, ?)") try (var statement =
.append(cidr.getCidr()).append(cidr.getStartIpInt()).append(cidr.getEndIpInt())) { Query.prepare(
"insert into `whitelisted-ranges` (`cidr_string`, `ip_start`, `ip_end`) values (?, ?, ?)")
.append(cidr.getCidr())
.append(cidr.getStartIpInt())
.append(cidr.getEndIpInt())) {
statement.execute(); statement.execute();
} catch (SQLException e) { } catch (SQLException e) {
AntiVPN.getInstance().getExecutor().logException("Could not add cidr '" + cidr + "' to whitelist due to SQL error.", e); AntiVPN.getInstance()
.getExecutor()
.logException("Could not add cidr '" + cidr + "' to whitelist due to SQL error.", e);
} }
} }
AntiVPN.getInstance().getExecutor().log("Removing %s old ranges from database...", rangesToRemove.size()); AntiVPN.getInstance()
.getExecutor()
.log("Removing %s old ranges from database...", rangesToRemove.size());
for (BigInteger[] range : rangesToRemove) { for (BigInteger[] range : rangesToRemove) {
try(var statement = Query.prepare("delete from `whitelisted-ranges` where `ip_start` = ? and `ip_end` = ?")) { try (var statement =
Query.prepare("delete from `whitelisted-ranges` where `ip_start` = ? and `ip_end` = ?")) {
statement.append(range[0]).append(range[1]).execute(); statement.append(range[0]).append(range[1]).execute();
} catch (SQLException e) { } 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()
.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()); AntiVPN.getInstance()
.getExecutor()
.log("Updating %s ranges to proper CIDR notation with the database", ipRanges.size());
for (CIDRUtils cidr : ipRanges) { for (CIDRUtils cidr : ipRanges) {
try(var statement = Query.prepare("update `whitelisted-ranges` set `cidr_string` = ? where `ip_start` = ? and `ip_end` = ?")) { try (var statement =
statement.append(cidr.getCidr()).append(cidr.getStartIpInt()).append(cidr.getEndIpInt()).execute(); 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) { } catch (SQLException e) {
AntiVPN.getInstance().getExecutor().logException("Could not update cidr '" + cidr + "' to proper CIDR notation in whitelist due to SQL error.", 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())) { try (var preparedStatement =
Query.prepare("INSERT INTO `database_version` (`version`) VALUES (?)")
.append(versionNumber())) {
preparedStatement.execute(); preparedStatement.execute();
} catch (SQLException e) { } catch (SQLException e) {
AntiVPN.getInstance().getExecutor().logException("Could not update database version to 2 due to SQL error.", e); AntiVPN.getInstance()
.getExecutor()
.logException("Could not update database version to 2 due to SQL error.", e);
} }
} }
@@ -105,7 +152,7 @@ public class Third implements Version<VPNDatabase> {
@Override @Override
public boolean needsUpdate(VPNDatabase database) { public boolean needsUpdate(VPNDatabase database) {
try (var statement = Query.prepare("select * from `database_version` where version = 2")) { try (var statement = Query.prepare("select * from `database_version` where version = 2")) {
try(var set = statement.executeQuery()) { try (var set = statement.executeQuery()) {
return !set.next(); return !set.next();
} }
} catch (SQLException e) { } catch (SQLException e) {
@@ -16,8 +16,6 @@
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;
@@ -30,15 +28,14 @@ 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 {
@@ -48,33 +45,48 @@ public class MongoVPN implements VPNDatabase {
public MongoDatabase antivpnDatabase; public MongoDatabase antivpnDatabase;
public MongoVPN() { public MongoVPN() {
AntiVPN.getInstance().getExecutor().getThreadExecutor().scheduleAtFixedRate(() -> { AntiVPN.getInstance()
if(!AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled()) return; .getExecutor()
.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().getExecutor().getWhitelisted() AntiVPN.getInstance()
.getExecutor()
.getWhitelisted()
.addAll(AntiVPN.getInstance().getDatabase().getAllWhitelisted()); .addAll(AntiVPN.getInstance().getDatabase().getAllWhitelisted());
//Refreshing whitlisted IPs // Refreshing whitlisted IPs
AntiVPN.getInstance().getExecutor().getWhitelistedIps().clear(); AntiVPN.getInstance().getExecutor().getWhitelistedIps().clear();
AntiVPN.getInstance().getExecutor().getWhitelistedIps() AntiVPN.getInstance()
.getExecutor()
.getWhitelistedIps()
.addAll(AntiVPN.getInstance().getDatabase().getAllWhitelistedIps()); .addAll(AntiVPN.getInstance().getDatabase().getAllWhitelistedIps());
}, 2, 30, TimeUnit.SECONDS); },
2,
30,
TimeUnit.SECONDS);
} }
@Override @Override
public Optional<VPNResponse> getStoredResponse(String ip) { public Optional<VPNResponse> getStoredResponse(String ip) {
Document rdoc = cacheDocument.find(Filters.eq("ip", ip)).first(); Document rdoc = cacheDocument.find(Filters.eq("ip", ip)).first();
if(rdoc != null) { if (rdoc != null) {
long lastUpdate = rdoc.get("lastAccess", 0L); long lastUpdate = rdoc.get("lastAccess", 0L);
if(System.currentTimeMillis() - lastUpdate > TimeUnit.HOURS.toMillis(1)) { if (System.currentTimeMillis() - lastUpdate > TimeUnit.HOURS.toMillis(1)) {
AntiVPN.getInstance().getExecutor().getThreadExecutor().execute(() -> deleteResponse(ip)); AntiVPN.getInstance().getExecutor().getThreadExecutor().execute(() -> deleteResponse(ip));
return null; return null;
} }
return Optional.of(VPNResponse.builder().asn(rdoc.getString("asn")).ip(ip) return Optional.of(
VPNResponse.builder()
.asn(rdoc.getString("asn"))
.ip(ip)
.countryName(rdoc.getString("countryName")) .countryName(rdoc.getString("countryName"))
.countryCode(rdoc.getString("countryCode")) .countryCode(rdoc.getString("countryCode"))
.city(rdoc.getString("city")) .city(rdoc.getString("city"))
@@ -94,7 +106,7 @@ public class MongoVPN implements VPNDatabase {
@Override @Override
public void cacheResponse(VPNResponse toCache) { public void cacheResponse(VPNResponse toCache) {
if(AntiVPN.getInstance().getVpnConfig().cachedResults()) { if (AntiVPN.getInstance().getVpnConfig().cachedResults()) {
Document rdoc = new Document("ip", toCache.getIp()); Document rdoc = new Document("ip", toCache.getIp());
rdoc.put("asn", toCache.getAsn()); rdoc.put("asn", toCache.getAsn());
@@ -111,10 +123,14 @@ public class MongoVPN implements VPNDatabase {
rdoc.put("longitude", toCache.getLongitude()); rdoc.put("longitude", toCache.getLongitude());
rdoc.put("lastAccess", System.currentTimeMillis()); rdoc.put("lastAccess", System.currentTimeMillis());
AntiVPN.getInstance().getExecutor().getThreadExecutor().execute(() -> { AntiVPN.getInstance()
.getExecutor()
.getThreadExecutor()
.execute(
() -> {
Bson update = new Document("$set", rdoc); Bson update = new Document("$set", rdoc);
cacheDocument.updateOne(Filters.eq("ip", toCache.getIp()), update, cacheDocument.updateOne(
new UpdateOptions().upsert(true)); Filters.eq("ip", toCache.getIp()), update, new UpdateOptions().upsert(true));
}); });
} }
} }
@@ -127,8 +143,11 @@ public class MongoVPN implements VPNDatabase {
@Override @Override
public boolean isWhitelisted(UUID uuid) { public boolean isWhitelisted(UUID uuid) {
return settingsDocument return settingsDocument
.find(Filters.and(Filters.eq("setting", "whitelist"), .find(
Filters.eq("uuid", uuid.toString()))).first() != null; Filters.and(
Filters.eq("setting", "whitelist"), Filters.eq("uuid", uuid.toString())))
.first()
!= null;
} }
@Override @Override
@@ -145,8 +164,14 @@ public class MongoVPN implements VPNDatabase {
public boolean isWhitelisted(CIDRUtils cidr) { public boolean isWhitelisted(CIDRUtils cidr) {
var start = new Decimal128(new BigDecimal(cidr.getStartIpInt())); var start = new Decimal128(new BigDecimal(cidr.getStartIpInt()));
var end = new Decimal128(new BigDecimal(cidr.getEndIpInt())); var end = new Decimal128(new BigDecimal(cidr.getEndIpInt()));
return settingsDocument.find(Filters.and(Filters.eq("setting", "whitelist"), return settingsDocument
Filters.lte("ip_start", start), Filters.gte("ip_end", end))).first() != null; .find(
Filters.and(
Filters.eq("setting", "whitelist"),
Filters.lte("ip_start", start),
Filters.gte("ip_end", end)))
.first()
!= null;
} }
@Override @Override
@@ -160,10 +185,8 @@ public class MongoVPN implements VPNDatabase {
@Override @Override
public void removeWhitelist(UUID uuid) { public void removeWhitelist(UUID uuid) {
AntiVPN.getInstance().getExecutor().getWhitelisted().remove(uuid); AntiVPN.getInstance().getExecutor().getWhitelisted().remove(uuid);
settingsDocument.deleteMany(Filters settingsDocument.deleteMany(
.and( Filters.and(Filters.eq("setting", "whitelist"), Filters.eq("uuid", uuid.toString())));
Filters.eq("setting", "whitelist"),
Filters.eq("uuid", uuid.toString())));
} }
@Override @Override
@@ -178,8 +201,8 @@ public class MongoVPN implements VPNDatabase {
@Override @Override
public void removeWhitelist(CIDRUtils cidr) { public void removeWhitelist(CIDRUtils cidr) {
settingsDocument.deleteMany(Filters settingsDocument.deleteMany(
.and( Filters.and(
Filters.eq("setting", "whitelist"), Filters.eq("setting", "whitelist"),
Filters.eq("ip_start", new Decimal128(new BigDecimal(cidr.getStartIpInt()))), Filters.eq("ip_start", new Decimal128(new BigDecimal(cidr.getStartIpInt()))),
Filters.eq("ip_end", new Decimal128(new BigDecimal(cidr.getEndIpInt()))))); Filters.eq("ip_end", new Decimal128(new BigDecimal(cidr.getEndIpInt())))));
@@ -188,22 +211,30 @@ public class MongoVPN implements VPNDatabase {
@Override @Override
public List<UUID> getAllWhitelisted() { public List<UUID> getAllWhitelisted() {
List<UUID> uuids = new ArrayList<>(); List<UUID> uuids = new ArrayList<>();
settingsDocument.find(Filters.and(Filters.eq("setting", "whitelist"), settingsDocument
Filters.exists("uuid"))) .find(Filters.and(Filters.eq("setting", "whitelist"), Filters.exists("uuid")))
.forEach((Consumer<? super Document>) doc -> uuids.add(UUID.fromString(doc.getString("uuid")))); .forEach(
(Consumer<? super Document>) doc -> uuids.add(UUID.fromString(doc.getString("uuid"))));
return uuids; return uuids;
} }
@Override @Override
public List<CIDRUtils> getAllWhitelistedIps() { public List<CIDRUtils> getAllWhitelistedIps() {
List<CIDRUtils> ips = new ArrayList<>(); List<CIDRUtils> ips = new ArrayList<>();
settingsDocument.find(Filters.and(Filters.eq("setting", "whitelist"), settingsDocument
Filters.exists("cidr_string"))).forEach((Consumer<? super Document>) doc -> { .find(Filters.and(Filters.eq("setting", "whitelist"), Filters.exists("cidr_string")))
.forEach(
(Consumer<? super Document>)
doc -> {
try { try {
var cidr = new CIDRUtils(doc.getString("cidr_string")); var cidr = new CIDRUtils(doc.getString("cidr_string"));
ips.add(cidr); ips.add(cidr);
} catch (UnknownHostException e) { } catch (UnknownHostException e) {
AntiVPN.getInstance().getExecutor().logException("Could not format ip " + doc.getString("cidr_string") + " into a CIDR!", e); AntiVPN.getInstance()
.getExecutor()
.logException(
"Could not format ip " + doc.getString("cidr_string") + " into a CIDR!",
e);
} }
}); });
return ips; return ips;
@@ -211,17 +242,32 @@ public class MongoVPN implements VPNDatabase {
@Override @Override
public void alertsState(UUID uuid, Consumer<Boolean> result) { public void alertsState(UUID uuid, Consumer<Boolean> result) {
AntiVPN.getInstance().getExecutor().getThreadExecutor().execute(() -> result.accept(settingsDocument AntiVPN.getInstance()
.find(Filters.and(Filters.eq("setting", "alerts"), .getExecutor()
Filters.eq("uuid", uuid.toString()))).first() != null)); .getThreadExecutor()
.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().getExecutor().getThreadExecutor().execute(() -> { AntiVPN.getInstance()
settingsDocument.deleteMany(Filters.and(Filters.eq("setting", "alerts"), .getExecutor()
Filters.eq("uuid", uuid.toString()))); .getThreadExecutor()
if(state) { .execute(
() -> {
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());
@@ -237,21 +283,27 @@ public class MongoVPN implements VPNDatabase {
@Override @Override
public void init() { public void init() {
if(!AntiVPN.getInstance().getVpnConfig().mongoDatabaseURL().isEmpty()) { //URL if (!AntiVPN.getInstance().getVpnConfig().mongoDatabaseURL().isEmpty()) { // URL
ConnectionString cs = new ConnectionString(AntiVPN.getInstance().getVpnConfig().mongoDatabaseURL()); ConnectionString cs =
MongoClientSettings settings = MongoClientSettings.builder().applyConnectionString(cs).build(); new ConnectionString(AntiVPN.getInstance().getVpnConfig().mongoDatabaseURL());
MongoClientSettings settings =
MongoClientSettings.builder().applyConnectionString(cs).build();
client = MongoClients.create(settings); client = MongoClients.create(settings);
} else { } else {
MongoClientSettings.Builder settingsBld = MongoClientSettings.builder().readPreference(ReadPreference.nearest()) MongoClientSettings.Builder settingsBld =
.applyToClusterSettings(builder -> builder. MongoClientSettings.builder()
hosts(Collections.singletonList( .readPreference(ReadPreference.nearest())
.applyToClusterSettings(
builder ->
builder.hosts(
Collections.singletonList(
new ServerAddress( new ServerAddress(
AntiVPN.getInstance().getVpnConfig().getIp(), AntiVPN.getInstance().getVpnConfig().getIp(),
AntiVPN.getInstance().getVpnConfig().getPort()) AntiVPN.getInstance().getVpnConfig().getPort()))));
))); if (AntiVPN.getInstance().getVpnConfig().useDatabaseCreds()) {
if(AntiVPN.getInstance().getVpnConfig().useDatabaseCreds()) { settingsBld.credential(
settingsBld.credential(MongoCredential MongoCredential.createCredential(
.createCredential(AntiVPN.getInstance().getVpnConfig().getUsername(), AntiVPN.getInstance().getVpnConfig().getUsername(),
AntiVPN.getInstance().getVpnConfig().getDatabaseName(), AntiVPN.getInstance().getVpnConfig().getDatabaseName(),
AntiVPN.getInstance().getVpnConfig().getPassword().toCharArray())); AntiVPN.getInstance().getVpnConfig().getPassword().toCharArray()));
} }
@@ -265,7 +317,7 @@ public class MongoVPN implements VPNDatabase {
cacheDocument = antivpnDatabase.getCollection("cache"); cacheDocument = antivpnDatabase.getCollection("cache");
for (Version<MongoVPN> mongoDbVersion : Version.mongoDbVersions) { for (Version<MongoVPN> mongoDbVersion : Version.mongoDbVersions) {
if(mongoDbVersion.needsUpdate(this)) { if (mongoDbVersion.needsUpdate(this)) {
mongoDbVersion.update(this); mongoDbVersion.update(this);
} }
} }
@@ -28,7 +28,7 @@ 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"));
@@ -23,22 +23,24 @@ 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.settingsDocument.find(Filters.and(Filters.eq("setting", "whitelist"), database
Filters.exists("ip"))) .settingsDocument
.forEach((Consumer<? super Document>) doc -> { .find(Filters.and(Filters.eq("setting", "whitelist"), Filters.exists("ip")))
.forEach(
(Consumer<? super Document>)
doc -> {
backup.add(new Document(doc)); backup.add(new Document(doc));
String ip = doc.getString("ip"); String ip = doc.getString("ip");
@@ -51,14 +53,16 @@ public class MongoSecond implements Version<MongoVPN> {
doc.append("cidr_string", cidr.toString()); doc.append("cidr_string", cidr.toString());
doc.remove("ip"); doc.remove("ip");
database.settingsDocument.replaceOne(Filters.eq("_id", doc.getObjectId("_id")), doc); database.settingsDocument.replaceOne(
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(Indexes.compoundIndex(Indexes.ascending("ip_start"), Indexes.ascending("ip_end"))); database.settingsDocument.createIndex(
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()));
@@ -66,7 +70,9 @@ public class MongoSecond implements Version<MongoVPN> {
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(doc -> database.settingsDocument.replaceOne(Filters.eq("_id", doc.getObjectId("_id")), doc)); toRollback.forEach(
doc ->
database.settingsDocument.replaceOne(Filters.eq("_id", doc.getObjectId("_id")), doc));
toRollback.clear(); toRollback.clear();
} }
@@ -23,9 +23,6 @@ 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;
@@ -33,6 +30,8 @@ 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
@@ -40,51 +39,81 @@ public class MongoThird implements Version<MongoVPN> {
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.settingsDocument.find(Filters.and(Filters.eq("setting", "whitelist"), Filters.exists("cidr_string"))) database
.forEach((Consumer<? super Document>) doc -> { .settingsDocument
BigInteger start = doc.get("ip_start", Decimal128.class).bigDecimalValue().toBigInteger(); .find(Filters.and(Filters.eq("setting", "whitelist"), Filters.exists("cidr_string")))
BigInteger end = doc.get("ip_end", Decimal128.class).bigDecimalValue().toBigInteger(); .forEach(
(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().getExecutor().log(Level.WARNING, "Found multiple CIDR ranges for whitelist range for %s, %s!", start, end); AntiVPN.getInstance()
.getExecutor()
.log(
Level.WARNING,
"Found multiple CIDR ranges for whitelist range for %s, %s!",
start,
end);
} else ipRanges.addAll(range); } else ipRanges.addAll(range);
} catch (UnknownHostException e) { } catch (UnknownHostException e) {
AntiVPN.getInstance().getExecutor().logException( AntiVPN.getInstance()
String.format("Could not convert ip range to CIDR! %s, %s", start, end), e); .getExecutor()
.logException(
String.format("Could not convert ip range to CIDR! %s, %s", start, end),
e);
} }
}); });
if(!rangesToInsert.isEmpty()) { if (!rangesToInsert.isEmpty()) {
AntiVPN.getInstance().getExecutor().log("Inserting %s new ranges into database...", rangesToInsert.size()); AntiVPN.getInstance()
var documentsToInsert = rangesToInsert.stream().map(cidr -> { .getExecutor()
.log("Inserting %s new ranges into database...", rangesToInsert.size());
var documentsToInsert =
rangesToInsert.stream()
.map(
cidr -> {
Document doc = new Document("setting", "whitelist"); Document doc = new Document("setting", "whitelist");
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.getCidr()); 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().getExecutor().log("Removing %s old ranges from database...", rangesToRemove.size()); AntiVPN.getInstance()
rangesToRemove.forEach(range -> database.settingsDocument .getExecutor()
.deleteMany(Filters.and( .log("Removing %s old ranges from database...", rangesToRemove.size());
rangesToRemove.forEach(
range ->
database.settingsDocument.deleteMany(
Filters.and(
Filters.gte("ip_start", new Decimal128(new BigDecimal(range[0]))), Filters.gte("ip_start", new Decimal128(new BigDecimal(range[0]))),
Filters.lte("ip_end", new Decimal128(new BigDecimal(range[1])))))); Filters.lte("ip_end", new Decimal128(new BigDecimal(range[1]))))));
} }
if(!ipRanges.isEmpty()) { if (!ipRanges.isEmpty()) {
AntiVPN.getInstance().getExecutor().log("Updating %s CIDRs in database with proper notation...", ipRanges.size()); AntiVPN.getInstance()
.getExecutor()
.log("Updating %s CIDRs in database with proper notation...", ipRanges.size());
ipRanges.forEach(cidr -> database.settingsDocument ipRanges.forEach(
.updateMany(Filters.and(Filters.eq("setting", "whitelist"), cidr ->
database.settingsDocument.updateMany(
Filters.and(
Filters.eq("setting", "whitelist"),
Filters.eq("ip_start", new Decimal128(new BigDecimal(cidr.getStartIpInt()))), Filters.eq("ip_start", new Decimal128(new BigDecimal(cidr.getStartIpInt()))),
Filters.eq("ip_end", new Decimal128(new BigDecimal(cidr.getEndIpInt())))), Filters.eq("ip_end", new Decimal128(new BigDecimal(cidr.getEndIpInt())))),
new Document("$set", new Document("cidr_string", cidr.getCidr())))); new Document("$set", new Document("cidr_string", cidr.getCidr()))));
@@ -20,40 +20,50 @@ 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().getExecutor().getThreadExecutor().scheduleAtFixedRate(() -> { AntiVPN.getInstance()
if(!AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled() || MySQL.isClosed()) return; .getExecutor()
.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().getExecutor().getWhitelisted() AntiVPN.getInstance()
.getExecutor()
.getWhitelisted()
.addAll(AntiVPN.getInstance().getDatabase().getAllWhitelisted()); .addAll(AntiVPN.getInstance().getDatabase().getAllWhitelisted());
//Refreshing whitlisted IPs // Refreshing whitlisted IPs
AntiVPN.getInstance().getExecutor().getWhitelistedIps().clear(); AntiVPN.getInstance().getExecutor().getWhitelistedIps().clear();
AntiVPN.getInstance().getExecutor().getWhitelistedIps() AntiVPN.getInstance()
.getExecutor()
.getWhitelistedIps()
.addAll(AntiVPN.getInstance().getDatabase().getAllWhitelistedIps()); .addAll(AntiVPN.getInstance().getDatabase().getAllWhitelistedIps());
}, 2, 30, TimeUnit.SECONDS); },
2,
30,
TimeUnit.SECONDS);
} }
@Override @Override
public void init() { public void init() {
if (!AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled()) if (!AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled()) return;
return;
AntiVPN.getInstance().getExecutor().log("Initializing MySQL..."); AntiVPN.getInstance().getExecutor().log("Initializing MySQL...");
MySQL.init(); MySQL.init();
AntiVPN.getInstance().getExecutor().log("Checking for updates..."); AntiVPN.getInstance().getExecutor().log("Checking for updates...");
//Running check for old table types to update // Running check for old table types to update
try { try {
for (Version<MySqlVPN> version : Version.mysqlVersions) { for (Version<MySqlVPN> version : Version.mysqlVersions) {
if(version.needsUpdate(this)) { if (version.needsUpdate(this)) {
version.update(this); version.update(this);
} }
} }
@@ -16,15 +16,13 @@
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 @Getter private final PreparedStatement statement;
private final PreparedStatement statement;
private int pos = 1; private int pos = 1;
public ExecutableStatement(PreparedStatement statement) { public ExecutableStatement(PreparedStatement statement) {
@@ -36,7 +34,7 @@ public class ExecutableStatement implements AutoCloseable {
} }
public void execute(ResultSetIterator iterator) throws SQLException { public void execute(ResultSetIterator iterator) throws SQLException {
try(var rs = statement.executeQuery()) { try (var rs = statement.executeQuery()) {
while (rs.next()) iterator.next(rs); while (rs.next()) iterator.next(rs);
} }
} }
@@ -18,9 +18,6 @@ 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;
@@ -29,6 +26,8 @@ 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;
@@ -36,8 +35,11 @@ public class MySQL {
public static void init() { public static void init() {
try { try {
if (conn == null || conn.isClosed()) { if (conn == null || conn.isClosed()) {
String url = "jdbc:mysql://" + AntiVPN.getInstance().getVpnConfig().getIp() String url =
+ ":" + AntiVPN.getInstance().getVpnConfig().getPort() "jdbc:mysql://"
+ AntiVPN.getInstance().getVpnConfig().getIp()
+ ":"
+ AntiVPN.getInstance().getVpnConfig().getPort()
+ "/?useSSL=true&autoReconnect=true"; + "/?useSSL=true&autoReconnect=true";
Properties properties = new Properties(); Properties properties = new Properties();
properties.setProperty("user", AntiVPN.getInstance().getVpnConfig().getUsername()); properties.setProperty("user", AntiVPN.getInstance().getVpnConfig().getUsername());
@@ -58,8 +60,11 @@ public class MySQL {
throw ex; throw ex;
} }
AntiVPN.getInstance().getExecutor().log( AntiVPN.getInstance()
"No permission to create MySQL database `" + databaseName .getExecutor()
.log(
"No permission to create MySQL database `"
+ databaseName
+ "`. Attempting to use the existing database instead."); + "`. Attempting to use the existing database instead.");
} }
@@ -67,7 +72,9 @@ public class MySQL {
AntiVPN.getInstance().getExecutor().log("Connection to MySQL has been established."); AntiVPN.getInstance().getExecutor().log("Connection to MySQL has been established.");
} }
} catch (Exception e) { } catch (Exception e) {
AntiVPN.getInstance().getExecutor().logException("Failed to load mysql: " + e.getMessage(), e); AntiVPN.getInstance()
.getExecutor()
.logException("Failed to load mysql: " + e.getMessage(), e);
throw new RuntimeException("Could not initialize MySQL connection", e); throw new RuntimeException("Could not initialize MySQL connection", e);
} }
} }
@@ -92,33 +99,46 @@ public class MySQL {
File databaseFile = new File(dataFolder, "database"); File databaseFile = new File(dataFolder, "database");
try { try {
conn = new NonClosableConnection(new org.h2.jdbc.JdbcConnection("jdbc:h2:file:" + conn =
databaseFile.getAbsolutePath(), new NonClosableConnection(
new Properties(), AntiVPN.getInstance().getVpnConfig().getUsername(), new org.h2.jdbc.JdbcConnection(
AntiVPN.getInstance().getVpnConfig().getPassword(), false)); "jdbc:h2:file:" + databaseFile.getAbsolutePath(),
new Properties(),
AntiVPN.getInstance().getVpnConfig().getUsername(),
AntiVPN.getInstance().getVpnConfig().getPassword(),
false));
conn.setAutoCommit(true); conn.setAutoCommit(true);
Query.use(conn); Query.use(conn);
AntiVPN.getInstance().getExecutor().log("Connection to H2 has been established."); AntiVPN.getInstance().getExecutor().log("Connection to H2 has been established.");
} catch (SQLException ex) { } catch (SQLException ex) {
AntiVPN.getInstance().getExecutor().logException("H2 exception on initialize", ex); AntiVPN.getInstance().getExecutor().logException("H2 exception on initialize", ex);
if(ex instanceof JdbcSQLFeatureNotSupportedException if (ex instanceof JdbcSQLFeatureNotSupportedException
|| ex instanceof JdbcSQLNonTransientConnectionException) { || ex instanceof JdbcSQLNonTransientConnectionException) {
AntiVPN.getInstance().getExecutor() AntiVPN.getInstance()
.log("H2 database file is incompatible with this version of AntiVPN. " + .getExecutor()
"Backing up old database file..."); .log(
"H2 database file is incompatible with this version of AntiVPN. "
+ "Backing up old database file...");
shutdown(); shutdown();
if (allowRetry && backupOldDB(dbFile, dataFolder)) { if (allowRetry && backupOldDB(dbFile, dataFolder)) {
initH2(false); initH2(false);
} else { } else {
AntiVPN.getInstance().getExecutor().log( AntiVPN.getInstance()
"Could not back up and remove the incompatible H2 database file automatically."); .getExecutor()
.log("Could not back up and remove the incompatible H2 database file automatically.");
} }
} else { } else {
AntiVPN.getInstance().getExecutor().logException("Failed to load H2 database: " + ex.getCause().toString(), ex); AntiVPN.getInstance()
.getExecutor()
.logException("Failed to load H2 database: " + ex.getCause().toString(), ex);
} }
} catch (Exception e) { } catch (Exception e) {
AntiVPN.getInstance().getExecutor().logException("Failed to load H2 database: " + e.getMessage(), e); AntiVPN.getInstance()
AntiVPN.getInstance().getExecutor().log(Level.INFO, "TIP: Try deleting the plugin folder and restarting your server!"); .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!");
} }
} }
@@ -128,13 +148,15 @@ public class MySQL {
} }
if (!dbFile.isFile()) { if (!dbFile.isFile()) {
AntiVPN.getInstance().getExecutor().log("Skipping backup for non-file path: " + dbFile.getAbsolutePath()); AntiVPN.getInstance()
.getExecutor()
.log("Skipping backup for non-file path: " + dbFile.getAbsolutePath());
return false; return false;
} }
try { try {
File backupDir = new File(dataFolder, "backups"); File backupDir = new File(dataFolder, "backups");
if(backupDir.mkdirs()) { if (backupDir.mkdirs()) {
AntiVPN.getInstance().getExecutor().log("Created backup directory"); AntiVPN.getInstance().getExecutor().log("Created backup directory");
} else if (backupDir.exists()) { } else if (backupDir.exists()) {
AntiVPN.getInstance().getExecutor().log("Backup directory already exists"); AntiVPN.getInstance().getExecutor().log("Backup directory already exists");
@@ -142,12 +164,15 @@ public class MySQL {
AntiVPN.getInstance().getExecutor().log("Could not create backup directory"); AntiVPN.getInstance().getExecutor().log("Could not create backup directory");
return false; return false;
} }
File backupFile = new File(backupDir, dbFile.getName() + ".backup_" + System.currentTimeMillis()); File backupFile =
new File(backupDir, dbFile.getName() + ".backup_" + System.currentTimeMillis());
Files.copy(dbFile.toPath(), backupFile.toPath()); Files.copy(dbFile.toPath(), backupFile.toPath());
if (!dbFile.delete()) { if (!dbFile.delete()) {
dbFile.deleteOnExit(); dbFile.deleteOnExit();
AntiVPN.getInstance().getExecutor().log("Could not delete database file - will try again on shutdown"); AntiVPN.getInstance()
.getExecutor()
.log("Could not delete database file - will try again on shutdown");
return false; return false;
} }
@@ -170,9 +195,9 @@ public class MySQL {
public static void shutdown() { public static void shutdown() {
try { try {
if(conn != null && !conn.isClosed()) { if (conn != null && !conn.isClosed()) {
if(conn instanceof NonClosableConnection) { if (conn instanceof NonClosableConnection) {
((NonClosableConnection)conn).shutdown(); ((NonClosableConnection) conn).shutdown();
} else conn.close(); } else conn.close();
conn = null; conn = null;
} }
@@ -182,8 +207,7 @@ public class MySQL {
} }
public static boolean isClosed() { public static boolean isClosed() {
if(conn == null) if (conn == null) return true;
return true;
try { try {
return conn.isClosed(); return conn.isClosed();
@@ -31,9 +31,7 @@ public class NonClosableConnection implements Connection {
this.delegate = delegate; this.delegate = delegate;
} }
/** /** Actually {@link #close() closes} the underlying connection. */
* Actually {@link #close() closes} the underlying connection.
*/
public final void shutdown() throws SQLException { public final void shutdown() throws SQLException {
this.delegate.close(); this.delegate.close();
} }
@@ -58,56 +56,268 @@ public class NonClosableConnection implements Connection {
} }
// Forward to the delegate connection // Forward to the delegate connection
@Override public Statement createStatement() throws SQLException { return this.delegate.createStatement(); } @Override
@Override public PreparedStatement prepareStatement(String sql) throws SQLException { return this.delegate.prepareStatement(sql); } public Statement createStatement() throws SQLException {
@Override public CallableStatement prepareCall(String sql) throws SQLException { return this.delegate.prepareCall(sql); } return this.delegate.createStatement();
@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 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();
}
} }
@@ -16,15 +16,13 @@
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 @Getter private static Connection conn;
private static Connection conn;
public static void use(Connection conn) { public static void use(Connection conn) {
Query.conn = conn; Query.conn = conn;
@@ -34,6 +32,4 @@ public class Query {
public static ExecutableStatement prepare(@Language("SQL") String sql) throws SQLException { public static ExecutableStatement prepare(@Language("SQL") String sql) throws SQLException {
return new ExecutableStatement(conn.prepareStatement(sql)); return new ExecutableStatement(conn.prepareStatement(sql));
} }
} }
@@ -21,26 +21,29 @@ 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 = Query.prepare("select `DATA_TYPE` from INFORMATION_SCHEMA.COLUMNS " + try (var statement =
"WHERE table_name = 'responses' AND COLUMN_NAME = 'isp';")) { Query.prepare(
statement.execute(set -> { "select `DATA_TYPE` from INFORMATION_SCHEMA.COLUMNS "
if(set.getObject("DATA_TYPE").toString().contains("varchar")) { + "WHERE table_name = 'responses' AND COLUMN_NAME = 'isp';")) {
AntiVPN.getInstance().getExecutor().log("Using old database format for storing responses! " + statement.execute(
"Dropping table and creating a new one..."); set -> {
try(var state = Query.prepare("drop table `responses`")) { if (set.getObject("DATA_TYPE").toString().contains("varchar")) {
if(state.execute() > 0) { AntiVPN.getInstance()
.getExecutor()
.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!"); AntiVPN.getInstance().getExecutor().log("Successfully dropped table!");
} }
} }
} }
}); });
} catch (SQLException e) { } catch (SQLException e) {
@@ -28,13 +28,15 @@ 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(); int versionNumber();
boolean needsUpdate(DB database); boolean needsUpdate(DB database);
Version<MongoVPN>[] mongoDbVersions = new Version[] {new MongoFirst(), new MongoSecond(), new MongoThird()}; Version<MongoVPN>[] mongoDbVersions =
new Version[] {new MongoFirst(), new MongoSecond(), new MongoThird()};
Version<MySqlVPN>[] mysqlVersions = new Version[] {new MySQLFirst(), new Second(), new Third()}; Version<MySqlVPN>[] mysqlVersions = new Version[] {new MySQLFirst(), new Second(), new Third()};
Version<H2VPN>[] h2Versions = new Version[] {new First(), new Second(), new Third()}; Version<H2VPN>[] h2Versions = new Version[] {new First(), new Second(), new Third()};
} }
@@ -20,6 +20,16 @@ import dev.brighten.antivpn.AntiVPN;
import dev.brighten.antivpn.utils.NonnullByDefault; import dev.brighten.antivpn.utils.NonnullByDefault;
import dev.brighten.antivpn.utils.Supplier; import dev.brighten.antivpn.utils.Supplier;
import dev.brighten.antivpn.utils.Suppliers; import dev.brighten.antivpn.utils.Suppliers;
import java.io.*;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.*;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.JarOutputStream;
import lombok.Getter; import lombok.Getter;
import org.objectweb.asm.AnnotationVisitor; import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassReader;
@@ -32,20 +42,9 @@ import org.objectweb.asm.RecordComponentVisitor;
import org.objectweb.asm.commons.ClassRemapper; import org.objectweb.asm.commons.ClassRemapper;
import org.objectweb.asm.commons.Remapper; import org.objectweb.asm.commons.Remapper;
import java.io.*;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.*;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.JarOutputStream;
/** /**
* Resolves {@link MavenLibrary} annotations for a class, and loads the dependency * Resolves {@link MavenLibrary} annotations for a class, and loads the dependency into the
* into the classloader. * classloader.
*/ */
@SuppressWarnings("CallToPrintStackTrace") @SuppressWarnings("CallToPrintStackTrace")
@NonnullByDefault @NonnullByDefault
@@ -54,20 +53,21 @@ public final class LibraryLoader {
private static final String RELOCATION_METADATA_PATH = "META-INF/antivpn-relocation.properties"; private static final String RELOCATION_METADATA_PATH = "META-INF/antivpn-relocation.properties";
@SuppressWarnings("Guava") @SuppressWarnings("Guava")
private static final Supplier<URLClassLoaderAccess> URL_INJECTOR = AntiVPN.getInstance().getClass().getClassLoader() instanceof URLClassLoader ? private static final Supplier<URLClassLoaderAccess> URL_INJECTOR =
Suppliers.memoize(() -> AntiVPN.getInstance().getClass().getClassLoader() instanceof URLClassLoader
URLClassLoaderAccess.create((URLClassLoader) AntiVPN.getInstance().getClass().getClassLoader())) ? Suppliers.memoize(
() ->
URLClassLoaderAccess.create(
(URLClassLoader) AntiVPN.getInstance().getClass().getClassLoader()))
: null; : null;
public static void loadAll(Object object) { public static void loadAll(Object object) {
if(URL_INJECTOR == null) if (URL_INJECTOR == null) return;
return;
loadAll(object.getClass()); loadAll(object.getClass());
} }
public static void loadAll(Class<?> clazz) { public static void loadAll(Class<?> clazz) {
if(URL_INJECTOR == null) if (URL_INJECTOR == null) return;
return;
MavenLibrary[] libs = clazz.getDeclaredAnnotationsByType(MavenLibrary.class); MavenLibrary[] libs = clazz.getDeclaredAnnotationsByType(MavenLibrary.class);
for (MavenLibrary lib : libs) { for (MavenLibrary lib : libs) {
@@ -77,17 +77,27 @@ public final class LibraryLoader {
relocations.put(relocate.from().replace("\\", ""), relocate.to()); relocations.put(relocate.from().replace("\\", ""), relocate.to());
} }
load(lib.groupId().replace("\\", ""), lib.artifactId(), lib.version(), lib.repo().url(), relocations); load(
lib.groupId().replace("\\", ""),
lib.artifactId(),
lib.version(),
lib.repo().url(),
relocations);
} }
} }
public static void load(String groupId, String artifactId, String version, String repoUrl, public static void load(
String groupId,
String artifactId,
String version,
String repoUrl,
Map<String, String> relocations) { Map<String, String> relocations) {
load(new Dependency(groupId, artifactId, version, repoUrl), relocations); load(new Dependency(groupId, artifactId, version, repoUrl), relocations);
} }
public static void load(Dependency d, Map<String, String> relocations) { public static void load(Dependency d, Map<String, String> relocations) {
System.out.printf("Loading dependency %s:%s:%s from %s%n", System.out.printf(
"Loading dependency %s:%s:%s from %s%n",
d.getGroupId(), d.getArtifactId(), d.getVersion(), d.getRepoUrl()); d.getGroupId(), d.getArtifactId(), d.getVersion(), d.getRepoUrl());
String name = d.getArtifactId() + "-" + d.getVersion(); String name = d.getArtifactId() + "-" + d.getVersion();
@@ -103,8 +113,10 @@ public final class LibraryLoader {
// Download the original jar if it doesn't exist // Download the original jar if it doesn't exist
if (!originalJar.exists()) { if (!originalJar.exists()) {
try { try {
System.out.println("Dependency '" + name + System.out.println(
"' is not already in the libraries folder. Attempting to download..."); "Dependency '"
+ name
+ "' is not already in the libraries folder. Attempting to download...");
URL url = d.getUrl(); URL url = d.getUrl();
try (InputStream is = url.openStream()) { try (InputStream is = url.openStream()) {
@@ -182,7 +194,8 @@ public final class LibraryLoader {
jos.write(relocatedBytes); jos.write(relocatedBytes);
jos.closeEntry(); jos.closeEntry();
} else { } else {
// Relocate package-scoped resources so ResourceBundle lookups follow relocated packages. // Relocate package-scoped resources so ResourceBundle lookups follow relocated
// packages.
String relocatedPath = relocateResourcePath(name, relocations); String relocatedPath = relocateResourcePath(name, relocations);
JarEntry newEntry = new JarEntry(relocatedPath); JarEntry newEntry = new JarEntry(relocatedPath);
@@ -202,8 +215,8 @@ public final class LibraryLoader {
jos.closeEntry(); jos.closeEntry();
} catch (Exception e) { } catch (Exception e) {
// Log but continue with other service files // Log but continue with other service files
System.out.println("Warning: Could not write service file " + System.out.println(
entry.getKey() + ": " + e.getMessage()); "Warning: Could not write service file " + entry.getKey() + ": " + e.getMessage());
} }
} }
@@ -213,7 +226,8 @@ public final class LibraryLoader {
validateRelocatedJar(targetJar, relocations); validateRelocatedJar(targetJar, relocations);
} }
private static boolean shouldRebuildRelocatedJar(File relocatedJar, Map<String, String> relocations) { private static boolean shouldRebuildRelocatedJar(
File relocatedJar, Map<String, String> relocations) {
if (!relocatedJar.exists()) { if (!relocatedJar.exists()) {
return true; return true;
} }
@@ -229,7 +243,8 @@ public final class LibraryLoader {
metadata.load(is); metadata.load(is);
} }
if (!String.valueOf(RELOCATION_FORMAT_VERSION).equals(metadata.getProperty("formatVersion"))) { if (!String.valueOf(RELOCATION_FORMAT_VERSION)
.equals(metadata.getProperty("formatVersion"))) {
return true; return true;
} }
@@ -266,9 +281,12 @@ public final class LibraryLoader {
jos.closeEntry(); jos.closeEntry();
} }
private static void processServiceFile(String name, InputStream is, private static void processServiceFile(
String name,
InputStream is,
Map<String, StringBuilder> serviceFiles, Map<String, StringBuilder> serviceFiles,
Map<String, String> relocations) throws IOException { Map<String, String> relocations)
throws IOException {
// Read service file content // Read service file content
String content = new String(readAllBytes(is)); String content = new String(readAllBytes(is));
StringBuilder contentBuilder = serviceFiles.computeIfAbsent(name, k -> new StringBuilder()); StringBuilder contentBuilder = serviceFiles.computeIfAbsent(name, k -> new StringBuilder());
@@ -279,8 +297,7 @@ public final class LibraryLoader {
if (!trimmed.isEmpty() && !trimmed.startsWith("#")) { if (!trimmed.isEmpty() && !trimmed.startsWith("#")) {
for (Map.Entry<String, String> relocation : relocations.entrySet()) { for (Map.Entry<String, String> relocation : relocations.entrySet()) {
if (trimmed.startsWith(relocation.getKey())) { if (trimmed.startsWith(relocation.getKey())) {
trimmed = relocation.getValue() + trimmed = relocation.getValue() + trimmed.substring(relocation.getKey().length());
trimmed.substring(relocation.getKey().length());
break; break;
} }
} }
@@ -289,14 +306,16 @@ public final class LibraryLoader {
} }
} }
private static byte[] relocateClass(String entryName, byte[] classBytes, Map<String, String> relocations) { private static byte[] relocateClass(
String entryName, byte[] classBytes, Map<String, String> relocations) {
try { try {
// Convert to slash notation for ASM // Convert to slash notation for ASM
Remapper prefixRemapper = getPrefixRemapper(relocations); Remapper prefixRemapper = getPrefixRemapper(relocations);
// Create custom ClassWriter to handle missing classes // Create custom ClassWriter to handle missing classes
ClassReader reader = new ClassReader(classBytes); ClassReader reader = new ClassReader(classBytes);
ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS) { ClassWriter writer =
new ClassWriter(ClassWriter.COMPUTE_MAXS) {
@Override @Override
protected String getCommonSuperClass(String type1, String type2) { protected String getCommonSuperClass(String type1, String type2) {
try { try {
@@ -308,7 +327,8 @@ public final class LibraryLoader {
} }
}; };
ClassVisitor visitor = createStringRelocationVisitor(new ClassRemapper(writer, prefixRemapper), relocations); ClassVisitor visitor =
createStringRelocationVisitor(new ClassRemapper(writer, prefixRemapper), relocations);
visitor = createMySqlUtilFallbackVisitor(entryName, visitor); visitor = createMySqlUtilFallbackVisitor(entryName, visitor);
// Process class with remapper // Process class with remapper
@@ -332,7 +352,8 @@ public final class LibraryLoader {
return className; return className;
} }
private static byte[] relocateUtf8Constants(byte[] classBytes, Map<String, String> relocations) throws IOException { private static byte[] relocateUtf8Constants(byte[] classBytes, Map<String, String> relocations)
throws IOException {
Map<String, String> dotMappings = new HashMap<>(); Map<String, String> dotMappings = new HashMap<>();
Map<String, String> slashMappings = new HashMap<>(); Map<String, String> slashMappings = new HashMap<>();
for (Map.Entry<String, String> entry : relocations.entrySet()) { for (Map.Entry<String, String> entry : relocations.entrySet()) {
@@ -421,22 +442,24 @@ public final class LibraryLoader {
}; };
} }
private static ClassVisitor createMySqlUtilFallbackVisitor(String entryName, ClassVisitor delegate) { private static ClassVisitor createMySqlUtilFallbackVisitor(
String entryName, ClassVisitor delegate) {
if (!"com/mysql/cj/util/Util.class".equals(entryName)) { if (!"com/mysql/cj/util/Util.class".equals(entryName)) {
return delegate; return delegate;
} }
return new ClassVisitor(Opcodes.ASM9, delegate) { return new ClassVisitor(Opcodes.ASM9, delegate) {
@Override @Override
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, public MethodVisitor visitMethod(
String[] exceptions) { int access, String name, String descriptor, String signature, String[] exceptions) {
MethodVisitor visitor = super.visitMethod(access, name, descriptor, signature, exceptions); MethodVisitor visitor = super.visitMethod(access, name, descriptor, signature, exceptions);
if (visitor == null) { if (visitor == null) {
return null; return null;
} }
if (!"getInstance".equals(name) if (!"getInstance".equals(name)
|| !"(Ljava/lang/Class;Ljava/lang/String;[Ljava/lang/Class;[Ljava/lang/Object;Lcom/mysql/cj/exceptions/ExceptionInterceptor;)Ljava/lang/Object;".equals(descriptor)) { || !"(Ljava/lang/Class;Ljava/lang/String;[Ljava/lang/Class;[Ljava/lang/Object;Lcom/mysql/cj/exceptions/ExceptionInterceptor;)Ljava/lang/Object;"
.equals(descriptor)) {
return visitor; return visitor;
} }
@@ -445,7 +468,8 @@ public final class LibraryLoader {
public void visitCode() { public void visitCode() {
super.visitCode(); super.visitCode();
super.visitVarInsn(Opcodes.ALOAD, 1); super.visitVarInsn(Opcodes.ALOAD, 1);
super.visitMethodInsn(Opcodes.INVOKESTATIC, super.visitMethodInsn(
Opcodes.INVOKESTATIC,
"dev/brighten/antivpn/depends/LibraryLoader", "dev/brighten/antivpn/depends/LibraryLoader",
"relocateReflectiveClassName", "relocateReflectiveClassName",
"(Ljava/lang/String;)Ljava/lang/String;", "(Ljava/lang/String;)Ljava/lang/String;",
@@ -457,8 +481,8 @@ public final class LibraryLoader {
}; };
} }
private static ClassVisitor createStringRelocationVisitor(ClassVisitor delegate, private static ClassVisitor createStringRelocationVisitor(
Map<String, String> relocations) { ClassVisitor delegate, Map<String, String> relocations) {
Map<String, String> dotMappings = new HashMap<>(); Map<String, String> dotMappings = new HashMap<>();
Map<String, String> slashMappings = new HashMap<>(); Map<String, String> slashMappings = new HashMap<>();
for (Map.Entry<String, String> entry : relocations.entrySet()) { for (Map.Entry<String, String> entry : relocations.entrySet()) {
@@ -469,18 +493,22 @@ public final class LibraryLoader {
return new ClassVisitor(Opcodes.ASM9, delegate) { return new ClassVisitor(Opcodes.ASM9, delegate) {
@Override @Override
public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) { public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
return wrapAnnotationVisitor(super.visitAnnotation(descriptor, visible), dotMappings, slashMappings); return wrapAnnotationVisitor(
super.visitAnnotation(descriptor, visible), dotMappings, slashMappings);
} }
@Override @Override
public AnnotationVisitor visitTypeAnnotation(int typeRef, org.objectweb.asm.TypePath typePath, public AnnotationVisitor visitTypeAnnotation(
String descriptor, boolean visible) { int typeRef, org.objectweb.asm.TypePath typePath, String descriptor, boolean visible) {
return wrapAnnotationVisitor(super.visitTypeAnnotation(typeRef, typePath, descriptor, visible), return wrapAnnotationVisitor(
dotMappings, slashMappings); super.visitTypeAnnotation(typeRef, typePath, descriptor, visible),
dotMappings,
slashMappings);
} }
@Override @Override
public RecordComponentVisitor visitRecordComponent(String name, String descriptor, String signature) { public RecordComponentVisitor visitRecordComponent(
String name, String descriptor, String signature) {
RecordComponentVisitor visitor = super.visitRecordComponent(name, descriptor, signature); RecordComponentVisitor visitor = super.visitRecordComponent(name, descriptor, signature);
if (visitor == null) { if (visitor == null) {
return null; return null;
@@ -488,22 +516,33 @@ public final class LibraryLoader {
return new RecordComponentVisitor(Opcodes.ASM9, visitor) { return new RecordComponentVisitor(Opcodes.ASM9, visitor) {
@Override @Override
public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) { public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
return wrapAnnotationVisitor(super.visitAnnotation(descriptor, visible), return wrapAnnotationVisitor(
dotMappings, slashMappings); super.visitAnnotation(descriptor, visible), dotMappings, slashMappings);
} }
@Override @Override
public AnnotationVisitor visitTypeAnnotation(int typeRef, org.objectweb.asm.TypePath typePath, public AnnotationVisitor visitTypeAnnotation(
String descriptor, boolean visible) { int typeRef,
return wrapAnnotationVisitor(super.visitTypeAnnotation(typeRef, typePath, descriptor, visible), org.objectweb.asm.TypePath typePath,
dotMappings, slashMappings); String descriptor,
boolean visible) {
return wrapAnnotationVisitor(
super.visitTypeAnnotation(typeRef, typePath, descriptor, visible),
dotMappings,
slashMappings);
} }
}; };
} }
@Override @Override
public FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value) { public FieldVisitor visitField(
FieldVisitor visitor = super.visitField(access, name, descriptor, signature, int access, String name, String descriptor, String signature, Object value) {
FieldVisitor visitor =
super.visitField(
access,
name,
descriptor,
signature,
relocateAsmValue(value, dotMappings, slashMappings)); relocateAsmValue(value, dotMappings, slashMappings));
if (visitor == null) { if (visitor == null) {
return null; return null;
@@ -511,22 +550,27 @@ public final class LibraryLoader {
return new FieldVisitor(Opcodes.ASM9, visitor) { return new FieldVisitor(Opcodes.ASM9, visitor) {
@Override @Override
public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) { public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
return wrapAnnotationVisitor(super.visitAnnotation(descriptor, visible), return wrapAnnotationVisitor(
dotMappings, slashMappings); super.visitAnnotation(descriptor, visible), dotMappings, slashMappings);
} }
@Override @Override
public AnnotationVisitor visitTypeAnnotation(int typeRef, org.objectweb.asm.TypePath typePath, public AnnotationVisitor visitTypeAnnotation(
String descriptor, boolean visible) { int typeRef,
return wrapAnnotationVisitor(super.visitTypeAnnotation(typeRef, typePath, descriptor, visible), org.objectweb.asm.TypePath typePath,
dotMappings, slashMappings); String descriptor,
boolean visible) {
return wrapAnnotationVisitor(
super.visitTypeAnnotation(typeRef, typePath, descriptor, visible),
dotMappings,
slashMappings);
} }
}; };
} }
@Override @Override
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, public MethodVisitor visitMethod(
String[] exceptions) { int access, String name, String descriptor, String signature, String[] exceptions) {
MethodVisitor visitor = super.visitMethod(access, name, descriptor, signature, exceptions); MethodVisitor visitor = super.visitMethod(access, name, descriptor, signature, exceptions);
if (visitor == null) { if (visitor == null) {
return null; return null;
@@ -534,53 +578,75 @@ public final class LibraryLoader {
return new MethodVisitor(Opcodes.ASM9, visitor) { return new MethodVisitor(Opcodes.ASM9, visitor) {
@Override @Override
public AnnotationVisitor visitAnnotationDefault() { public AnnotationVisitor visitAnnotationDefault() {
return wrapAnnotationVisitor(super.visitAnnotationDefault(), dotMappings, slashMappings); return wrapAnnotationVisitor(
super.visitAnnotationDefault(), dotMappings, slashMappings);
} }
@Override @Override
public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) { public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
return wrapAnnotationVisitor(super.visitAnnotation(descriptor, visible), return wrapAnnotationVisitor(
dotMappings, slashMappings); super.visitAnnotation(descriptor, visible), dotMappings, slashMappings);
} }
@Override @Override
public AnnotationVisitor visitTypeAnnotation(int typeRef, org.objectweb.asm.TypePath typePath, public AnnotationVisitor visitTypeAnnotation(
String descriptor, boolean visible) { int typeRef,
return wrapAnnotationVisitor(super.visitTypeAnnotation(typeRef, typePath, descriptor, visible), org.objectweb.asm.TypePath typePath,
dotMappings, slashMappings); String descriptor,
}
@Override
public AnnotationVisitor visitParameterAnnotation(int parameter, String descriptor,
boolean visible) { boolean visible) {
return wrapAnnotationVisitor(super.visitParameterAnnotation(parameter, descriptor, visible), return wrapAnnotationVisitor(
dotMappings, slashMappings); super.visitTypeAnnotation(typeRef, typePath, descriptor, visible),
dotMappings,
slashMappings);
} }
@Override @Override
public AnnotationVisitor visitInsnAnnotation(int typeRef, org.objectweb.asm.TypePath typePath, public AnnotationVisitor visitParameterAnnotation(
String descriptor, boolean visible) { int parameter, String descriptor, boolean visible) {
return wrapAnnotationVisitor(super.visitInsnAnnotation(typeRef, typePath, descriptor, visible), return wrapAnnotationVisitor(
dotMappings, slashMappings); super.visitParameterAnnotation(parameter, descriptor, visible),
dotMappings,
slashMappings);
} }
@Override @Override
public AnnotationVisitor visitTryCatchAnnotation(int typeRef, org.objectweb.asm.TypePath typePath, public AnnotationVisitor visitInsnAnnotation(
String descriptor, boolean visible) { int typeRef,
return wrapAnnotationVisitor(super.visitTryCatchAnnotation(typeRef, typePath, descriptor, visible), org.objectweb.asm.TypePath typePath,
dotMappings, slashMappings); String descriptor,
boolean visible) {
return wrapAnnotationVisitor(
super.visitInsnAnnotation(typeRef, typePath, descriptor, visible),
dotMappings,
slashMappings);
} }
@Override @Override
public AnnotationVisitor visitLocalVariableAnnotation(int typeRef, public AnnotationVisitor visitTryCatchAnnotation(
int typeRef,
org.objectweb.asm.TypePath typePath,
String descriptor,
boolean visible) {
return wrapAnnotationVisitor(
super.visitTryCatchAnnotation(typeRef, typePath, descriptor, visible),
dotMappings,
slashMappings);
}
@Override
public AnnotationVisitor visitLocalVariableAnnotation(
int typeRef,
org.objectweb.asm.TypePath typePath, org.objectweb.asm.TypePath typePath,
org.objectweb.asm.Label[] start, org.objectweb.asm.Label[] start,
org.objectweb.asm.Label[] end, org.objectweb.asm.Label[] end,
int[] index, String descriptor, int[] index,
String descriptor,
boolean visible) { boolean visible) {
return wrapAnnotationVisitor( return wrapAnnotationVisitor(
super.visitLocalVariableAnnotation(typeRef, typePath, start, end, index, descriptor, visible), super.visitLocalVariableAnnotation(
dotMappings, slashMappings); typeRef, typePath, start, end, index, descriptor, visible),
dotMappings,
slashMappings);
} }
@Override @Override
@@ -589,11 +655,15 @@ public final class LibraryLoader {
} }
@Override @Override
public void visitInvokeDynamicInsn(String name, String descriptor, org.objectweb.asm.Handle bootstrapMethodHandle, public void visitInvokeDynamicInsn(
String name,
String descriptor,
org.objectweb.asm.Handle bootstrapMethodHandle,
Object... bootstrapMethodArguments) { Object... bootstrapMethodArguments) {
Object[] relocatedArgs = new Object[bootstrapMethodArguments.length]; Object[] relocatedArgs = new Object[bootstrapMethodArguments.length];
for (int i = 0; i < bootstrapMethodArguments.length; i++) { for (int i = 0; i < bootstrapMethodArguments.length; i++) {
relocatedArgs[i] = relocateAsmValue(bootstrapMethodArguments[i], dotMappings, slashMappings); relocatedArgs[i] =
relocateAsmValue(bootstrapMethodArguments[i], dotMappings, slashMappings);
} }
super.visitInvokeDynamicInsn(name, descriptor, bootstrapMethodHandle, relocatedArgs); super.visitInvokeDynamicInsn(name, descriptor, bootstrapMethodHandle, relocatedArgs);
} }
@@ -602,7 +672,8 @@ public final class LibraryLoader {
}; };
} }
private static AnnotationVisitor wrapAnnotationVisitor(AnnotationVisitor delegate, private static AnnotationVisitor wrapAnnotationVisitor(
AnnotationVisitor delegate,
Map<String, String> dotMappings, Map<String, String> dotMappings,
Map<String, String> slashMappings) { Map<String, String> slashMappings) {
if (delegate == null) { if (delegate == null) {
@@ -617,7 +688,8 @@ public final class LibraryLoader {
@Override @Override
public AnnotationVisitor visitAnnotation(String name, String descriptor) { public AnnotationVisitor visitAnnotation(String name, String descriptor) {
return wrapAnnotationVisitor(super.visitAnnotation(name, descriptor), dotMappings, slashMappings); return wrapAnnotationVisitor(
super.visitAnnotation(name, descriptor), dotMappings, slashMappings);
} }
@Override @Override
@@ -627,8 +699,8 @@ public final class LibraryLoader {
}; };
} }
private static Object relocateAsmValue(Object value, Map<String, String> dotMappings, private static Object relocateAsmValue(
Map<String, String> slashMappings) { Object value, Map<String, String> dotMappings, Map<String, String> slashMappings) {
if (value instanceof String stringValue) { if (value instanceof String stringValue) {
return relocateStringValue(stringValue, dotMappings, slashMappings); return relocateStringValue(stringValue, dotMappings, slashMappings);
} }
@@ -636,8 +708,8 @@ public final class LibraryLoader {
return value; return value;
} }
private static String relocateStringValue(String value, Map<String, String> dotMappings, private static String relocateStringValue(
Map<String, String> slashMappings) { String value, Map<String, String> dotMappings, Map<String, String> slashMappings) {
for (Map.Entry<String, String> entry : dotMappings.entrySet()) { for (Map.Entry<String, String> entry : dotMappings.entrySet()) {
String from = entry.getKey(); String from = entry.getKey();
String relocated = relocateByPrefixes(value, from, entry.getValue(), '.', '$'); String relocated = relocateByPrefixes(value, from, entry.getValue(), '.', '$');
@@ -674,7 +746,8 @@ public final class LibraryLoader {
return value; return value;
} }
private static String relocateByPrefixes(String value, String from, String to, char... delimiters) { private static String relocateByPrefixes(
String value, String from, String to, char... delimiters) {
if (value.equals(from)) { if (value.equals(from)) {
return to; return to;
} }
@@ -688,14 +761,16 @@ public final class LibraryLoader {
return value; return value;
} }
private static void validateRelocatedJar(File targetJar, Map<String, String> relocations) throws IOException { private static void validateRelocatedJar(File targetJar, Map<String, String> relocations)
throws IOException {
Set<String> relocatedPrefixes = new HashSet<>(); Set<String> relocatedPrefixes = new HashSet<>();
Map<String, String> dotMappings = new HashMap<>(); Map<String, String> dotMappings = new HashMap<>();
Map<String, String> slashMappings = new HashMap<>(); Map<String, String> slashMappings = new HashMap<>();
for (Map.Entry<String, String> relocation : relocations.entrySet()) { for (Map.Entry<String, String> relocation : relocations.entrySet()) {
relocatedPrefixes.add(relocation.getValue().replace('.', '/') + "/"); relocatedPrefixes.add(relocation.getValue().replace('.', '/') + "/");
dotMappings.put(relocation.getKey(), relocation.getValue()); dotMappings.put(relocation.getKey(), relocation.getValue());
slashMappings.put(relocation.getKey().replace('.', '/'), relocation.getValue().replace('.', '/')); slashMappings.put(
relocation.getKey().replace('.', '/'), relocation.getValue().replace('.', '/'));
} }
try (JarFile jar = new JarFile(targetJar)) { try (JarFile jar = new JarFile(targetJar)) {
@@ -725,8 +800,12 @@ public final class LibraryLoader {
} }
} }
private static void findUnrelocatedConstant(String entryName, byte[] classBytes, Map<String, String> dotMappings, private static void findUnrelocatedConstant(
Map<String, String> slashMappings) throws IOException { String entryName,
byte[] classBytes,
Map<String, String> dotMappings,
Map<String, String> slashMappings)
throws IOException {
DataInputStream in = new DataInputStream(new ByteArrayInputStream(classBytes)); DataInputStream in = new DataInputStream(new ByteArrayInputStream(classBytes));
in.readInt(); in.readInt();
in.readUnsignedShort(); in.readUnsignedShort();
@@ -740,8 +819,11 @@ public final class LibraryLoader {
String value = in.readUTF(); String value = in.readUTF();
String relocated = relocateStringValue(value, dotMappings, slashMappings); String relocated = relocateStringValue(value, dotMappings, slashMappings);
if (!value.equals(relocated)) { if (!value.equals(relocated)) {
throw new IOException("Relocated jar still contains original reference '" + value throw new IOException(
+ "' in class entry " + entryName); "Relocated jar still contains original reference '"
+ value
+ "' in class entry "
+ entryName);
} }
} }
case 3, 4 -> in.readInt(); case 3, 4 -> in.readInt();
@@ -758,7 +840,9 @@ public final class LibraryLoader {
in.readUnsignedByte(); in.readUnsignedByte();
in.readUnsignedShort(); in.readUnsignedShort();
} }
default -> throw new IOException("Unknown constant pool tag " + tag + " while validating " + entryName); default ->
throw new IOException(
"Unknown constant pool tag " + tag + " while validating " + entryName);
} }
} }
} }
@@ -817,7 +901,7 @@ public final class LibraryLoader {
private static File getLibFolder() { private static File getLibFolder() {
File pluginDataFolder = AntiVPN.getInstance().getPluginFolder(); File pluginDataFolder = AntiVPN.getInstance().getPluginFolder();
File libs = new File(pluginDataFolder, "libraries"); File libs = new File(pluginDataFolder, "libraries");
if(libs.mkdirs()) { if (libs.mkdirs()) {
System.out.println("Created libraries folder!"); System.out.println("Created libraries folder!");
} }
return libs; return libs;
@@ -825,7 +909,7 @@ public final class LibraryLoader {
@Getter @Getter
@NonnullByDefault @NonnullByDefault
// Fix the Dependency class to preserve original groupId for downloading // Fix the Dependency class to preserve original groupId for downloading
public static final class Dependency { public static final class Dependency {
private final String groupId; private final String groupId;
private final String artifactId; private final String artifactId;
@@ -852,13 +936,17 @@ public final class LibraryLoader {
repo += "%s/%s/%s/%s-%s.jar"; repo += "%s/%s/%s/%s-%s.jar";
// Always use original groupId for Maven repository URL // Always use original groupId for Maven repository URL
String url = String.format(repo, this.originalGroupId.replace(".", "/"), String url =
this.originalArtifactId, this.version, this.originalArtifactId, this.version); String.format(
repo,
this.originalGroupId.replace(".", "/"),
this.originalArtifactId,
this.version,
this.originalArtifactId,
this.version);
return new URL(url); return new URL(url);
} }
// Rest of the class unchanged // Rest of the class unchanged
} }
} }
@@ -18,14 +18,11 @@ 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,9 +18,7 @@ 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)
@@ -56,5 +54,4 @@ public @interface MavenLibrary {
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
} }
@@ -26,5 +26,6 @@ import java.lang.annotation.Target;
@Target({}) @Target({})
public @interface Relocate { public @interface Relocate {
String from(); String from();
String to(); String to();
} }
@@ -18,9 +18,7 @@ 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)
@@ -32,5 +30,4 @@ public @interface Repository {
* @return the base url of the repository * @return the base url of the repository
*/ */
String url(); String url();
} }
@@ -22,9 +22,7 @@ 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 {
/** /**
@@ -49,7 +47,6 @@ public abstract class URLClassLoaderAccess {
this.classLoader = classLoader; this.classLoader = classLoader;
} }
/** /**
* Adds the given URL to the class loader. * Adds the given URL to the class loader.
* *
@@ -57,9 +54,7 @@ public abstract class URLClassLoaderAccess {
*/ */
public abstract void addURL(URL url); public abstract void addURL(URL url);
/** /** Accesses using reflection, not supported on Java 9+. */
* Accesses using reflection, not supported on Java 9+.
*/
private static class Reflection extends URLClassLoaderAccess { private static class Reflection extends URLClassLoaderAccess {
private static final Method ADD_URL_METHOD; private static final Method ADD_URL_METHOD;
@@ -137,7 +132,8 @@ public abstract class URLClassLoaderAccess {
this.pathURLs = pathURLs; this.pathURLs = pathURLs;
} }
private static Object fetchField(final Class<?> clazz, final Object object, final String name) throws NoSuchFieldException { private static Object fetchField(final Class<?> clazz, final Object object, final String name)
throws NoSuchFieldException {
Field field = clazz.getDeclaredField(name); Field field = clazz.getDeclaredField(name);
long offset = UNSAFE.objectFieldOffset(field); long offset = UNSAFE.objectFieldOffset(field);
return UNSAFE.getObject(object, offset); return UNSAFE.getObject(object, offset);
@@ -162,5 +158,4 @@ public abstract class URLClassLoaderAccess {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
} }
} }
@@ -17,7 +17,6 @@
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;
@@ -26,7 +25,7 @@ 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 + "\"");
} }
@@ -51,12 +50,18 @@ public class MessageHandler {
} }
public void initStrings(Function<VpnString, String> getter) { public void initStrings(Function<VpnString, String> getter) {
addString(new VpnString("command-misc-playerRequired", addString(
"&cYou must be a player to execute this command!"), getter); new VpnString(
addString(new VpnString("command-alerts-toggled", "command-misc-playerRequired", "&cYou must be a player to execute this command!"),
"&7Your player proxy notifications have been set to: &e%state%"), getter); getter);
addString(new VpnString("command-reload-complete", addString(
"&aSuccessfully reloaded KauriVPN plugin!"), getter); new VpnString(
"command-alerts-toggled",
"&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); addString(new VpnString("no-permission", "&cNo permission."), getter);
} }
} }
@@ -17,20 +17,18 @@
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 key;
private final String defaultMessage; private final String defaultMessage;
private String message; private String message;
@Setter @Setter private Function<VpnString, String> configStringGetter;
private Function<VpnString, String> configStringGetter;
public VpnString(String key, String defaultMessage) { public VpnString(String key, String defaultMessage) {
this.key = key; this.key = key;
@@ -39,7 +37,8 @@ public class VpnString {
@SneakyThrows @SneakyThrows
public void updateString() { public void updateString() {
if(configStringGetter == null) throw new Exception("The configStringGetter for string " + key + " is null!"); if (configStringGetter == null)
throw new Exception("The configStringGetter for string " + key + " is null!");
message = configStringGetter.apply(this); message = configStringGetter.apply(this);
} }
@@ -48,8 +47,9 @@ public class VpnString {
String formatted = configStringGetter.apply(this); String formatted = configStringGetter.apply(this);
for (Var<String, Object> replacement : replacements) { for (Var<String, Object> replacement : replacements) {
formatted = formatted formatted =
.replace("%" + replacement.getKey() + "%", replacement.getReplacement().toString()); formatted.replace(
"%" + replacement.getKey() + "%", replacement.getReplacement().toString());
} }
return formatted; return formatted;
@@ -59,8 +59,9 @@ public class VpnString {
String formatted = message; String formatted = message;
for (Var<String, Object> replacement : replacements) { for (Var<String, Object> replacement : replacements) {
formatted = formatted formatted =
.replace("%" + replacement.getKey() + "%", replacement.getReplacement().toString()); formatted.replace(
"%" + replacement.getKey() + "%", replacement.getReplacement().toString());
} }
player.sendMessage(formatted); player.sendMessage(formatted);
} }
@@ -16,18 +16,16 @@
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 * A class that enables to get an IP range from CIDR specification. It supports both IPv4 and IPv6.
* both IPv4 and IPv6.
*/ */
@Getter @Getter
public class CIDRUtils { public class CIDRUtils {
@@ -40,14 +38,13 @@ public class CIDRUtils {
private InetAddress endAddress; private InetAddress endAddress;
private final int prefixLength; private final int prefixLength;
public CIDRUtils(String cidr) throws UnknownHostException { public CIDRUtils(String cidr) throws UnknownHostException {
this.cidr = cidr; this.cidr = cidr;
/* split CIDR to address and prefix part */ /* split CIDR to address and prefix part */
if (this.cidr.contains("/")) { if (this.cidr.contains("/")) {
int index = this.cidr.indexOf("/"); int index = this.cidr.indexOf('/');
String addressPart = this.cidr.substring(0, index); String addressPart = this.cidr.substring(0, index);
String networkPart = this.cidr.substring(index + 1); String networkPart = this.cidr.substring(index + 1);
@@ -60,21 +57,15 @@ public class CIDRUtils {
} }
} }
private void calculate() throws UnknownHostException { private void calculate() throws UnknownHostException {
ByteBuffer maskBuffer; ByteBuffer maskBuffer;
int targetSize; int targetSize;
if (inetAddress.getAddress().length == 4) { if (inetAddress.getAddress().length == 4) {
maskBuffer = maskBuffer = ByteBuffer.allocate(4).putInt(-1);
ByteBuffer
.allocate(4)
.putInt(-1);
targetSize = 4; targetSize = 4;
} else { } else {
maskBuffer = ByteBuffer.allocate(16) maskBuffer = ByteBuffer.allocate(16).putLong(-1L).putLong(-1L);
.putLong(-1L)
.putLong(-1L);
targetSize = 16; targetSize = 16;
} }
@@ -93,7 +84,6 @@ public class CIDRUtils {
this.startAddress = InetAddress.getByAddress(startIpArr); this.startAddress = InetAddress.getByAddress(startIpArr);
this.endAddress = InetAddress.getByAddress(endIpArr); this.endAddress = InetAddress.getByAddress(endIpArr);
} }
private byte[] toBytes(byte[] array, int targetSize) { private byte[] toBytes(byte[] array, int targetSize) {
@@ -27,8 +27,7 @@ public class ConfigDefault<A> {
private final AntiVPN plugin; private final AntiVPN plugin;
public A get() { public A get() {
if(plugin.getConfig().get(path) != null) if (plugin.getConfig().get(path) != null) return (A) plugin.getConfig().get(path);
return (A) plugin.getConfig().get(path);
else { else {
plugin.getConfig().set(path, defaultValue); plugin.getConfig().set(path, defaultValue);
plugin.saveConfig(); plugin.saveConfig();
@@ -16,17 +16,15 @@
package dev.brighten.antivpn.utils; package dev.brighten.antivpn.utils;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.Map; import java.util.Map;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor @RequiredArgsConstructor
public class EvictingMap<K, V> extends LinkedHashMap<K, V> { public class EvictingMap<K, V> extends LinkedHashMap<K, V> {
@Getter @Getter private final int size;
private final int size;
@Override @Override
protected boolean removeEldestEntry(Map.Entry<K, V> eldest) { protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
@@ -29,10 +29,11 @@ public class IpUtils {
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))); }
} catch(Exception e) { return Optional.of(new BigDecimal(ipv6ToDecimalFormat(address)));
} catch (Exception e) {
return Optional.empty(); return Optional.empty();
} }
} }
@@ -74,6 +75,7 @@ public class IpUtils {
public static boolean isIpv6(BigDecimal ip) { public static boolean isIpv6(BigDecimal ip) {
return ip.compareTo(BigDecimal.valueOf(4294967295L)) > 0; return ip.compareTo(BigDecimal.valueOf(4294967295L)) > 0;
} }
public static boolean isIpv4(String ip) { public static boolean isIpv4(String ip) {
return ip.matches("^(?:[0-9]{1,3}\\.){3}[0-9]{1,3}$"); return ip.matches("^(?:[0-9]{1,3}\\.){3}[0-9]{1,3}$");
} }
@@ -83,7 +85,8 @@ public class IpUtils {
} }
public static boolean isIpv6(String 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}|:))?$"); 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) { public static String getIpv4(BigDecimal ip) {
@@ -105,5 +108,4 @@ public class IpUtils {
public static BigInteger ipv6ToDecimalFormat(String ipAddress) throws UnknownHostException { public static BigInteger ipv6ToDecimalFormat(String ipAddress) throws UnknownHostException {
return new BigInteger(1, Inet6Address.getByName(ipAddress).getAddress()); return new BigInteger(1, Inet6Address.getByName(ipAddress).getAddress());
} }
} }
@@ -20,7 +20,6 @@ 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.*;
@@ -32,9 +31,12 @@ import java.util.regex.Pattern;
public class MiscUtils { public class MiscUtils {
private static final Pattern ipv4 = Pattern.compile("[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}"); private static final Pattern ipv4 =
private static final String DEFAULT_FUNKEMUNKY_UUID_ENDPOINT = "https://funkemunky.cc/mojang/uuid?name="; Pattern.compile("[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}");
private static final String DEFAULT_MOJANG_UUID_ENDPOINT = "https://api.mojang.com/users/profiles/minecraft/"; private static final String DEFAULT_FUNKEMUNKY_UUID_ENDPOINT =
"https://funkemunky.cc/mojang/uuid?name=";
private static final String DEFAULT_MOJANG_UUID_ENDPOINT =
"https://api.mojang.com/users/profiles/minecraft/";
private static volatile String funkemunkyUuidEndpoint = DEFAULT_FUNKEMUNKY_UUID_ENDPOINT; private static volatile String funkemunkyUuidEndpoint = DEFAULT_FUNKEMUNKY_UUID_ENDPOINT;
private static volatile String mojangUuidEndpoint = DEFAULT_MOJANG_UUID_ENDPOINT; private static volatile String mojangUuidEndpoint = DEFAULT_MOJANG_UUID_ENDPOINT;
@@ -60,8 +62,7 @@ public class MiscUtils {
int lenght; int lenght;
byte[] buf = new byte[1024]; byte[] buf = new byte[1024];
while ((lenght = in.read(buf)) > 0) while ((lenght = in.read(buf)) > 0) {
{
out.write(buf, 0, lenght); out.write(buf, 0, lenght);
} }
@@ -80,12 +81,14 @@ public class MiscUtils {
}; };
} }
public static List<CIDRUtils> rangeToCidrs(BigInteger start, BigInteger end) throws UnknownHostException { public static List<CIDRUtils> rangeToCidrs(BigInteger start, BigInteger end)
throws UnknownHostException {
List<CIDRUtils> cidrs = new ArrayList<>(); List<CIDRUtils> cidrs = new ArrayList<>();
while (start.compareTo(end) <= 0) { while (start.compareTo(end) <= 0) {
// Find the number of trailing zero bits — this determines max block size alignment // Find the number of trailing zero bits — this determines max block size alignment
int trailingZeros = start.equals(BigInteger.ZERO) int trailingZeros =
start.equals(BigInteger.ZERO)
? 128 // handle the edge case ? 128 // handle the edge case
: start.getLowestSetBit(); : start.getLowestSetBit();
@@ -124,14 +127,18 @@ public class MiscUtils {
return uuid; return uuid;
} }
} catch (IOException | JSONException | URISyntaxException e) { } catch (IOException | JSONException | URISyntaxException e) {
AntiVPN.getInstance().getExecutor().logException("Error while looking up UUID for " + playername + "! Falling back to Mojang API", e); AntiVPN.getInstance()
.getExecutor()
.logException(
"Error while looking up UUID for " + playername + "! Falling back to Mojang API", e);
return lookupMojangUuid(playername); return lookupMojangUuid(playername);
} }
return null; return null;
} }
private static UUID lookupUuidFromUrl(String url) throws IOException, JSONException, URISyntaxException { private static UUID lookupUuidFromUrl(String url)
throws IOException, JSONException, URISyntaxException {
HttpURLConnection connection = (HttpURLConnection) new URI(url).toURL().openConnection(); HttpURLConnection connection = (HttpURLConnection) new URI(url).toURL().openConnection();
connection.setConnectTimeout(5000); connection.setConnectTimeout(5000);
connection.setReadTimeout(5000); connection.setReadTimeout(5000);
@@ -146,7 +153,10 @@ public class MiscUtils {
} }
try (InputStream inputStream = connection.getInputStream()) { try (InputStream inputStream = connection.getInputStream()) {
JSONObject object = new JSONObject(JsonReader.readAll(new InputStreamReader(inputStream, java.nio.charset.StandardCharsets.UTF_8))); JSONObject object =
new JSONObject(
JsonReader.readAll(
new InputStreamReader(inputStream, java.nio.charset.StandardCharsets.UTF_8)));
if (object.has("uuid")) { if (object.has("uuid")) {
return parseUuid(object.getString("uuid")); return parseUuid(object.getString("uuid"));
} }
@@ -160,10 +170,10 @@ public class MiscUtils {
private static UUID parseUuid(String value) { private static UUID parseUuid(String value) {
if (value.length() == 32) { if (value.length() == 32) {
value = value.replaceFirst( 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})", "([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" "$1-$2-$3-$4-$5");
);
} }
return UUID.fromString(value); return UUID.fromString(value);
@@ -173,7 +183,9 @@ public class MiscUtils {
try { try {
return lookupUuidFromUrl(mojangUuidEndpoint + playerName); return lookupUuidFromUrl(mojangUuidEndpoint + playerName);
} catch (IOException | JSONException | URISyntaxException e) { } catch (IOException | JSONException | URISyntaxException e) {
AntiVPN.getInstance().getExecutor().logException("Error while looking up UUID for " + playerName + " from Mojang!:", e); AntiVPN.getInstance()
.getExecutor()
.logException("Error while looking up UUID for " + playerName + " from Mojang!:", e);
} }
return null; return null;
@@ -188,8 +200,8 @@ public class MiscUtils {
funkemunkyUuidEndpoint = DEFAULT_FUNKEMUNKY_UUID_ENDPOINT; funkemunkyUuidEndpoint = DEFAULT_FUNKEMUNKY_UUID_ENDPOINT;
mojangUuidEndpoint = DEFAULT_MOJANG_UUID_ENDPOINT; mojangUuidEndpoint = DEFAULT_MOJANG_UUID_ENDPOINT;
} }
public static boolean isIpv4(String ip)
{ public static boolean isIpv4(String ip) {
return ipv4.matcher(ip).matches(); return ipv4.matcher(ip).matches();
} }
} }
@@ -22,6 +22,4 @@ import java.lang.annotation.RetentionPolicy;
@Documented @Documented
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
public @interface NonnullByDefault { public @interface NonnullByDefault {}
}
@@ -22,8 +22,7 @@
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) { public static <T> T checkNotNull(T reference) {
if (reference == null) { if (reference == null) {
@@ -41,7 +40,8 @@ public final class Preconditions {
} }
} }
public static <T> T checkNotNull(T reference, String errorMessageTemplate, Object... errorMessageArgs) { public static <T> T checkNotNull(
T reference, String errorMessageTemplate, Object... errorMessageArgs) {
if (reference == null) { if (reference == null) {
throw new NullPointerException(format(errorMessageTemplate, errorMessageArgs)); throw new NullPointerException(format(errorMessageTemplate, errorMessageArgs));
} else { } else {
@@ -209,7 +209,8 @@ public final class Preconditions {
} }
} }
public static <T> T checkNotNull(T obj, String errorMessageTemplate, Object p1, Object p2, Object p3) { public static <T> T checkNotNull(
T obj, String errorMessageTemplate, Object p1, Object p2, Object p3) {
if (obj == null) { if (obj == null) {
throw new NullPointerException(format(errorMessageTemplate, p1, p2, p3)); throw new NullPointerException(format(errorMessageTemplate, p1, p2, p3));
} else { } else {
@@ -217,7 +218,8 @@ public final class Preconditions {
} }
} }
public static <T> T checkNotNull(T obj, String errorMessageTemplate, Object p1, Object p2, Object p3, Object p4) { public static <T> T checkNotNull(
T obj, String errorMessageTemplate, Object p1, Object p2, Object p3, Object p4) {
if (obj == null) { if (obj == null) {
throw new NullPointerException(format(errorMessageTemplate, p1, p2, p3, p4)); throw new NullPointerException(format(errorMessageTemplate, p1, p2, p3, p4));
} else { } else {
@@ -232,7 +234,7 @@ public final class Preconditions {
int i; int i;
int placeholderStart; int placeholderStart;
for(i = 0; i < args.length; templateStart = placeholderStart + 2) { for (i = 0; i < args.length; templateStart = placeholderStart + 2) {
placeholderStart = template.indexOf("%s", templateStart); placeholderStart = template.indexOf("%s", templateStart);
if (placeholderStart == -1) { if (placeholderStart == -1) {
break; break;
@@ -247,7 +249,7 @@ public final class Preconditions {
builder.append(" ["); builder.append(" [");
builder.append(args[i++]); builder.append(args[i++]);
while(i < args.length) { while (i < args.length) {
builder.append(", "); builder.append(", ");
builder.append(args[i++]); builder.append(args[i++]);
} }
@@ -29,7 +29,10 @@ public class StringUtil {
} }
public static String varReplace(String input, APIPlayer player, VPNResponse result) { public static String varReplace(String input, APIPlayer player, VPNResponse result) {
return translateAlternateColorCodes('&', input.replace("%player%", player.getName()) return translateAlternateColorCodes(
'&',
input
.replace("%player%", player.getName())
.replace("%reason%", result.getMethod()) .replace("%reason%", result.getMethod())
.replace("%country%", result.getCountryName()) .replace("%country%", result.getCountryName())
.replace("%city%", result.getCity())); .replace("%city%", result.getCity()));
@@ -38,7 +41,7 @@ public class StringUtil {
public static String translateAlternateColorCodes(char altColorChar, String textToTranslate) { public static String translateAlternateColorCodes(char altColorChar, String textToTranslate) {
char[] b = textToTranslate.toCharArray(); char[] b = textToTranslate.toCharArray();
for(int i = 0; i < b.length - 1; ++i) { for (int i = 0; i < b.length - 1; ++i) {
if (b[i] == altColorChar && "0123456789AaBbCcDdEeFfKkLlMmNnOoRr".indexOf(b[i + 1]) > -1) { if (b[i] == altColorChar && "0123456789AaBbCcDdEeFfKkLlMmNnOoRr".indexOf(b[i + 1]) > -1) {
b[i] = 167; b[i] = 167;
b[i + 1] = Character.toLowerCase(b[i + 1]); b[i + 1] = Character.toLowerCase(b[i + 1]);
@@ -16,13 +16,13 @@
package dev.brighten.antivpn.utils; package dev.brighten.antivpn.utils;
import java.io.Serial;
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.
* *
@@ -96,8 +96,7 @@ public final class Suppliers {
+ ")"; + ")";
} }
@Serial @Serial private static final long serialVersionUID = 0;
private static final long serialVersionUID = 0;
} }
static class NonSerializableMemoizingSupplier<T> implements Supplier<T> { static class NonSerializableMemoizingSupplier<T> implements Supplier<T> {
@@ -16,6 +16,4 @@
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,40 +18,36 @@ 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) public Configuration(Configuration defaults) {
{ this(new LinkedHashMap<String, Object>(), defaults);
this( new LinkedHashMap<String, Object>(), defaults );
} }
Configuration(Map<?, ?> map, Configuration defaults) Configuration(Map<?, ?> map, Configuration defaults) {
{
this.self = new LinkedHashMap<>(); this.self = new LinkedHashMap<>();
this.defaults = defaults; this.defaults = defaults;
comments = new HashMap<>(); comments = new HashMap<>();
for ( Map.Entry<?, ?> entry : map.entrySet() ) for (Map.Entry<?, ?> entry : map.entrySet()) {
{ String key = (entry.getKey() == null) ? "null" : entry.getKey().toString();
String key = ( entry.getKey() == null ) ? "null" : entry.getKey().toString();
if ( entry.getValue() instanceof Map ) if (entry.getValue() instanceof Map) {
{ this.self.put(
this.self.put( key, new Configuration( (Map) entry.getValue(), ( defaults == null ) ? null : defaults.getSection( key ) ) ); key,
} else new Configuration(
{ (Map) entry.getValue(), (defaults == null) ? null : defaults.getSection(key)));
this.self.put( key, entry.getValue() ); } else {
this.self.put(key, entry.getValue());
} }
} }
} }
@@ -65,30 +61,29 @@ public final class Configuration
String currentPath = ""; String currentPath = "";
int lineNumber = 0; int lineNumber = 0;
for(Iterator<String> iterator = list.iterator(); iterator.hasNext(); lineNumber++) { for (Iterator<String> iterator = list.iterator(); iterator.hasNext(); lineNumber++) {
String line = iterator.next(); String line = iterator.next();
String trimmed = line.trim(); String trimmed = line.trim();
if(trimmed.startsWith("#") || trimmed.isEmpty()) { if (trimmed.startsWith("#") || trimmed.isEmpty()) {
addCommentLine(currentPath, line); addCommentLine(currentPath, line);
continue; continue;
} }
if(!line.isEmpty()) { if (!line.isEmpty()) {
if(line.contains(":")) { if (line.contains(":")) {
int layerFromLine = getLayerFromLine(line, lineNumber); int layerFromLine = getLayerFromLine(line, lineNumber);
if(layerFromLine < currentLayer) { if (layerFromLine < currentLayer) {
currentPath = regressPathBy(currentLayer - layerFromLine, currentPath); currentPath = regressPathBy(currentLayer - layerFromLine, currentPath);
} }
String key = getKeyFromLine(line); String key = getKeyFromLine(line);
if(currentLayer == 0) { if (currentLayer == 0) {
currentPath = key; currentPath = key;
} } else {
else {
currentPath += "." + key; currentPath += "." + key;
} }
} }
@@ -99,7 +94,7 @@ public final class Configuration
private void addCommentLine(String currentPath, String line) { private void addCommentLine(String currentPath, String line) {
List<String> list = comments.get(currentPath); List<String> list = comments.get(currentPath);
if(list == null) { if (list == null) {
list = new ArrayList<>(); list = new ArrayList<>();
} }
list.add(line); list.add(line);
@@ -110,8 +105,8 @@ public final class Configuration
String getKeyFromLine(String line) { String getKeyFromLine(String line) {
String key = null; String key = null;
for(int i = 0; i < line.length(); i++) { for (int i = 0; i < line.length(); i++) {
if(line.charAt(i) == ':') { if (line.charAt(i) == ':') {
key = line.substring(0, i); key = line.substring(0, i);
break; break;
} }
@@ -121,15 +116,15 @@ public final class Configuration
} }
String regressPathBy(int i, String currentPath) { String regressPathBy(int i, String currentPath) {
if(i <= 0) { if (i <= 0) {
return currentPath; return currentPath;
} }
String[] split = currentPath.split("\\."); String[] split = currentPath.split("\\.");
String rebuild = ""; String rebuild = "";
for(int j = 0; j < split.length - i; j++) { for (int j = 0; j < split.length - i; j++) {
rebuild += split[j]; rebuild += split[j];
if(j <= (split.length - j)) { if (j <= (split.length - j)) {
rebuild += "."; rebuild += ".";
} }
} }
@@ -140,109 +135,94 @@ public final class Configuration
int getLayerFromLine(String line, int lineNumber) { int getLayerFromLine(String line, int lineNumber) {
double d = 0; double d = 0;
for(int i = 0; i < line.length(); i++) { for (int i = 0; i < line.length(); i++) {
if(line.charAt(i) == ' ') { if (line.charAt(i) == ' ') {
d += 0.5; d += 0.5;
} } else {
else {
break; break;
} }
} }
return (int) d; return (int) d;
} }
private Configuration getSectionFor(String path) private Configuration getSectionFor(String path) {
{ int index = path.indexOf(SEPARATOR);
int index = path.indexOf( SEPARATOR ); if (index == -1) {
if ( index == -1 )
{
return this; return this;
} }
String root = path.substring( 0, index ); String root = path.substring(0, index);
Object section = self.get( root ); Object section = self.get(root);
if ( section == null ) if (section == null) {
{ section = new Configuration((defaults == null) ? null : defaults.getSection(root));
section = new Configuration( ( defaults == null ) ? null : defaults.getSection( root ) ); self.put(root, section);
self.put( root, section );
} }
return (Configuration) section; return (Configuration) section;
} }
private String getChild(String path) private String getChild(String path) {
{ int index = path.indexOf(SEPARATOR);
int index = path.indexOf( SEPARATOR ); return (index == -1) ? path : path.substring(index + 1);
return ( index == -1 ) ? path : path.substring( index + 1 );
} }
/*------------------------------------------------------------------------*/ /*------------------------------------------------------------------------*/
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public <T> T get(String path, T def) public <T> T get(String path, T def) {
{ Configuration section = getSectionFor(path);
Configuration section = getSectionFor( path );
Object val; Object val;
if ( section == this ) if (section == this) {
{ val = self.get(path);
val = self.get( path ); } else {
} else val = section.get(getChild(path), def);
{
val = section.get( getChild( path ), def );
} }
if ( val == null && def instanceof Configuration ) if (val == null && def instanceof Configuration) {
{ self.put(path, def);
self.put( path, def );
} }
return ( val != null ) ? (T) val : def; return (val != null) ? (T) val : def;
} }
public boolean contains(String path) public boolean contains(String path) {
{ return get(path, null) != null;
return get( path, null ) != null;
} }
public Object get(String path) public Object get(String path) {
{ return get(path, getDefault(path));
return get( path, getDefault( path ) );
} }
public Object getDefault(String path) public Object getDefault(String path) {
{ return (defaults == null) ? null : defaults.get(path);
return ( defaults == null ) ? null : defaults.get( path );
} }
public void set(String path, Object value) public void set(String path, Object value) {
{ if (value instanceof Map) {
if ( value instanceof Map ) value = new Configuration((Map) value, (defaults == null) ? null : defaults.getSection(path));
{
value = new Configuration( (Map) value, ( defaults == null ) ? null : defaults.getSection( path ) );
} }
Configuration section = getSectionFor( path ); Configuration section = getSectionFor(path);
if ( section == this ) if (section == this) {
{ if (value == null) {
if ( value == null ) self.remove(path);
{ } else {
self.remove( path ); self.put(path, value);
} else
{
self.put( path, value );
} }
} else } else {
{ section.set(getChild(path), value);
section.set( getChild( path ), value );
} }
} }
/*------------------------------------------------------------------------*/ /*------------------------------------------------------------------------*/
public Configuration getSection(String path) public Configuration getSection(String path) {
{ Object def = getDefault(path);
Object def = getDefault( path ); return (Configuration)
return (Configuration) get( path, ( def instanceof Configuration ) ? def : new Configuration( ( defaults == null ) ? null : defaults.getSection( path ) ) ); get(
path,
(def instanceof Configuration)
? def
: new Configuration((defaults == null) ? null : defaults.getSection(path)));
} }
/** /**
@@ -250,258 +230,212 @@ public final class Configuration
* *
* @return top level keys for this section * @return top level keys for this section
*/ */
public Collection<String> getKeys() public Collection<String> getKeys() {
{ return new LinkedHashSet<>(self.keySet());
return new LinkedHashSet<>( self.keySet() );
} }
/*------------------------------------------------------------------------*/ /*------------------------------------------------------------------------*/
public byte getByte(String path) public byte getByte(String path) {
{ Object def = getDefault(path);
Object def = getDefault( path ); return getByte(path, (def instanceof Number) ? ((Number) def).byteValue() : 0);
return getByte( path, ( def instanceof Number ) ? ( (Number) def ).byteValue() : 0 );
} }
public byte getByte(String path, byte def) public byte getByte(String path, byte def) {
{ Object val = get(path, def);
Object val = get( path, def ); return (val instanceof Number) ? ((Number) val).byteValue() : def;
return ( val instanceof Number ) ? ( (Number) val ).byteValue() : def;
} }
public List<Byte> getByteList(String path) public List<Byte> getByteList(String path) {
{ List<?> list = getList(path);
List<?> list = getList( path );
List<Byte> result = new ArrayList<>(); List<Byte> result = new ArrayList<>();
for ( Object object : list ) for (Object object : list) {
{ if (object instanceof Number) {
if ( object instanceof Number ) result.add(((Number) object).byteValue());
{
result.add( ( (Number) object ).byteValue() );
} }
} }
return result; return result;
} }
public short getShort(String path) public short getShort(String path) {
{ Object def = getDefault(path);
Object def = getDefault( path ); return getShort(path, (def instanceof Number) ? ((Number) def).shortValue() : 0);
return getShort( path, ( def instanceof Number ) ? ( (Number) def ).shortValue() : 0 );
} }
public short getShort(String path, short def) public short getShort(String path, short def) {
{ Object val = get(path, def);
Object val = get( path, def ); return (val instanceof Number) ? ((Number) val).shortValue() : def;
return ( val instanceof Number ) ? ( (Number) val ).shortValue() : def;
} }
public List<Short> getShortList(String path) public List<Short> getShortList(String path) {
{ List<?> list = getList(path);
List<?> list = getList( path );
List<Short> result = new ArrayList<>(); List<Short> result = new ArrayList<>();
for ( Object object : list ) for (Object object : list) {
{ if (object instanceof Number) {
if ( object instanceof Number ) result.add(((Number) object).shortValue());
{
result.add( ( (Number) object ).shortValue() );
} }
} }
return result; return result;
} }
public int getInt(String path) public int getInt(String path) {
{ Object def = getDefault(path);
Object def = getDefault( path ); return getInt(path, (def instanceof Number) ? ((Number) def).intValue() : 0);
return getInt( path, ( def instanceof Number ) ? ( (Number) def ).intValue() : 0 );
} }
public int getInt(String path, int def) public int getInt(String path, int def) {
{ Object val = get(path, def);
Object val = get( path, def ); return (val instanceof Number) ? ((Number) val).intValue() : def;
return ( val instanceof Number ) ? ( (Number) val ).intValue() : def;
} }
public List<Integer> getIntList(String path) public List<Integer> getIntList(String path) {
{ List<?> list = getList(path);
List<?> list = getList( path );
List<Integer> result = new ArrayList<>(); List<Integer> result = new ArrayList<>();
for ( Object object : list ) for (Object object : list) {
{ if (object instanceof Number) {
if ( object instanceof Number ) result.add(((Number) object).intValue());
{
result.add( ( (Number) object ).intValue() );
} }
} }
return result; return result;
} }
public long getLong(String path) public long getLong(String path) {
{ Object def = getDefault(path);
Object def = getDefault( path ); return getLong(path, (def instanceof Number) ? ((Number) def).longValue() : 0);
return getLong( path, ( def instanceof Number ) ? ( (Number) def ).longValue() : 0 );
} }
public long getLong(String path, long def) public long getLong(String path, long def) {
{ Object val = get(path, def);
Object val = get( path, def ); return (val instanceof Number) ? ((Number) val).longValue() : def;
return ( val instanceof Number ) ? ( (Number) val ).longValue() : def;
} }
public List<Long> getLongList(String path) public List<Long> getLongList(String path) {
{ List<?> list = getList(path);
List<?> list = getList( path );
List<Long> result = new ArrayList<>(); List<Long> result = new ArrayList<>();
for ( Object object : list ) for (Object object : list) {
{ if (object instanceof Number) {
if ( object instanceof Number ) result.add(((Number) object).longValue());
{
result.add( ( (Number) object ).longValue() );
} }
} }
return result; return result;
} }
public float getFloat(String path) public float getFloat(String path) {
{ Object def = getDefault(path);
Object def = getDefault( path ); return getFloat(path, (def instanceof Number) ? ((Number) def).floatValue() : 0);
return getFloat( path, ( def instanceof Number ) ? ( (Number) def ).floatValue() : 0 );
} }
public float getFloat(String path, float def) public float getFloat(String path, float def) {
{ Object val = get(path, def);
Object val = get( path, def ); return (val instanceof Number) ? ((Number) val).floatValue() : def;
return ( val instanceof Number ) ? ( (Number) val ).floatValue() : def;
} }
public List<Float> getFloatList(String path) public List<Float> getFloatList(String path) {
{ List<?> list = getList(path);
List<?> list = getList( path );
List<Float> result = new ArrayList<>(); List<Float> result = new ArrayList<>();
for ( Object object : list ) for (Object object : list) {
{ if (object instanceof Number) {
if ( object instanceof Number ) result.add(((Number) object).floatValue());
{
result.add( ( (Number) object ).floatValue() );
} }
} }
return result; return result;
} }
public double getDouble(String path) public double getDouble(String path) {
{ Object def = getDefault(path);
Object def = getDefault( path ); return getDouble(path, (def instanceof Number) ? ((Number) def).doubleValue() : 0);
return getDouble( path, ( def instanceof Number ) ? ( (Number) def ).doubleValue() : 0 );
} }
public double getDouble(String path, double def) public double getDouble(String path, double def) {
{ Object val = get(path, def);
Object val = get( path, def ); return (val instanceof Number) ? ((Number) val).doubleValue() : def;
return ( val instanceof Number ) ? ( (Number) val ).doubleValue() : def;
} }
public List<Double> getDoubleList(String path) public List<Double> getDoubleList(String path) {
{ List<?> list = getList(path);
List<?> list = getList( path );
List<Double> result = new ArrayList<>(); List<Double> result = new ArrayList<>();
for ( Object object : list ) for (Object object : list) {
{ if (object instanceof Number) {
if ( object instanceof Number ) result.add(((Number) object).doubleValue());
{
result.add( ( (Number) object ).doubleValue() );
} }
} }
return result; return result;
} }
public boolean getBoolean(String path) public boolean getBoolean(String path) {
{ Object def = getDefault(path);
Object def = getDefault( path ); return getBoolean(path, (def instanceof Boolean) ? (Boolean) def : false);
return getBoolean( path, ( def instanceof Boolean ) ? (Boolean) def : false );
} }
public boolean getBoolean(String path, boolean def) public boolean getBoolean(String path, boolean def) {
{ Object val = get(path, def);
Object val = get( path, def ); return (val instanceof Boolean) ? (Boolean) val : def;
return ( val instanceof Boolean ) ? (Boolean) val : def;
} }
public List<Boolean> getBooleanList(String path) public List<Boolean> getBooleanList(String path) {
{ List<?> list = getList(path);
List<?> list = getList( path );
List<Boolean> result = new ArrayList<>(); List<Boolean> result = new ArrayList<>();
for ( Object object : list ) for (Object object : list) {
{ if (object instanceof Boolean) {
if ( object instanceof Boolean ) result.add((Boolean) object);
{
result.add( (Boolean) object );
} }
} }
return result; return result;
} }
public char getChar(String path) public char getChar(String path) {
{ Object def = getDefault(path);
Object def = getDefault( path ); return getChar(path, (def instanceof Character) ? (Character) def : '\u0000');
return getChar( path, ( def instanceof Character ) ? (Character) def : '\u0000' );
} }
public char getChar(String path, char def) public char getChar(String path, char def) {
{ Object val = get(path, def);
Object val = get( path, def ); return (val instanceof Character) ? (Character) val : def;
return ( val instanceof Character ) ? (Character) val : def;
} }
public List<Character> getCharList(String path) public List<Character> getCharList(String path) {
{ List<?> list = getList(path);
List<?> list = getList( path );
List<Character> result = new ArrayList<>(); List<Character> result = new ArrayList<>();
for ( Object object : list ) for (Object object : list) {
{ if (object instanceof Character) {
if ( object instanceof Character ) result.add((Character) object);
{
result.add( (Character) object );
} }
} }
return result; return result;
} }
public String getString(String path) public String getString(String path) {
{ Object def = getDefault(path);
Object def = getDefault( path ); return getString(path, (def instanceof String) ? (String) def : "");
return getString( path, ( def instanceof String ) ? (String) def : "" );
} }
public String getString(String path, String def) public String getString(String path, String def) {
{ Object val = get(path, def);
Object val = get( path, def ); return (val instanceof String) ? (String) val : def;
return ( val instanceof String ) ? (String) val : def;
} }
public List<String> getStringList(String path) public List<String> getStringList(String path) {
{ List<?> list = getList(path);
List<?> list = getList( path );
List<String> result = new ArrayList<>(); List<String> result = new ArrayList<>();
for ( Object object : list ) for (Object object : list) {
{ if (object instanceof String) {
if ( object instanceof String ) result.add((String) object);
{
result.add( (String) object );
} }
} }
@@ -509,15 +443,13 @@ public final class Configuration
} }
/*------------------------------------------------------------------------*/ /*------------------------------------------------------------------------*/
public List<?> getList(String path) public List<?> getList(String path) {
{ Object def = getDefault(path);
Object def = getDefault( path ); return getList(path, (def instanceof List<?>) ? (List<?>) def : Collections.EMPTY_LIST);
return getList( path, ( def instanceof List<?> ) ? (List<?>) def : Collections.EMPTY_LIST );
} }
public List<?> getList(String path, List<?> def) public List<?> getList(String path, List<?> def) {
{ Object val = get(path, def);
Object val = get( path, def ); return (val instanceof List<?>) ? (List<?>) val : def;
return ( val instanceof List<?> ) ? (List<?>) val : def;
} }
} }
@@ -20,26 +20,22 @@ 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 = new HashMap<>(); public static final Map<Class<? extends ConfigurationProvider>, ConfigurationProvider> providers =
new HashMap<>();
static static {
{ try {
try providers.put(YamlConfiguration.class, new YamlConfiguration());
{ } catch (NoClassDefFoundError ex) {
providers.put( YamlConfiguration.class, new YamlConfiguration() );
} catch ( NoClassDefFoundError ex )
{
ex.printStackTrace(); ex.printStackTrace();
// Ignore, no SnakeYAML // 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 );
} }
/*------------------------------------------------------------------------*/ /*------------------------------------------------------------------------*/
@@ -16,6 +16,10 @@
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;
@@ -25,46 +29,38 @@ 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 = new ThreadLocal<Yaml>() private final ThreadLocal<Yaml> yaml =
{ new ThreadLocal<Yaml>() {
@Override @Override
protected Yaml initialValue() protected Yaml initialValue() {
{
DumperOptions options = new DumperOptions(); DumperOptions options = new DumperOptions();
options.setDefaultFlowStyle( DumperOptions.FlowStyle.BLOCK ); options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
Representer representer = 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 =
try ( Writer writer = new OutputStreamWriter( new FileOutputStream( file ), StandardCharsets.UTF_8 ) ) new OutputStreamWriter(new FileOutputStream(file), StandardCharsets.UTF_8)) {
{ save(config, writer);
save( config, writer );
} }
} }
@Override @Override
public void save(Configuration config, Writer writer) public void save(Configuration config, Writer writer) {
{
String contents = this.yaml.get().dump(config.self); String contents = this.yaml.get().dump(config.self);
if (contents.equals("{}\n")) { if (contents.equals("{}\n")) {
contents = ""; contents = "";
@@ -79,7 +75,7 @@ public class YamlConfiguration extends ConfigurationProvider
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
int lineNumber = 0; int lineNumber = 0;
for(Iterator<String> iterator = list.iterator(); iterator.hasNext(); lineNumber++) { for (Iterator<String> iterator = list.iterator(); iterator.hasNext(); lineNumber++) {
String line = iterator.next(); String line = iterator.next();
sb.append(line); sb.append(line);
sb.append('\n'); sb.append('\n');
@@ -90,7 +86,9 @@ public class YamlConfiguration extends ConfigurationProvider
int layerFromLine = config.getLayerFromLine(line, lineNumber); int layerFromLine = config.getLayerFromLine(line, lineNumber);
if (layerFromLine < currentLayer) { if (layerFromLine < currentLayer) {
currentPath = new StringBuilder(config.regressPathBy(currentLayer - layerFromLine, currentPath.toString())); currentPath =
new StringBuilder(
config.regressPathBy(currentLayer - layerFromLine, currentPath.toString()));
} }
String key = config.getKeyFromLine(line); String key = config.getKeyFromLine(line);
@@ -103,7 +101,11 @@ public class YamlConfiguration extends ConfigurationProvider
String path = currentPath.toString(); String path = currentPath.toString();
if (config.comments.containsKey(path)) { if (config.comments.containsKey(path)) {
config.comments.get(path).forEach(string -> { config
.comments
.get(path)
.forEach(
string -> {
sb.append(string); sb.append(string);
sb.append('\n'); sb.append('\n');
}); });
@@ -118,37 +120,34 @@ public class YamlConfiguration extends ConfigurationProvider
e.printStackTrace(); e.printStackTrace();
} }
} }
@Override @Override
public Configuration load(File file) throws IOException public Configuration load(File file) throws IOException {
{ return load(file, null);
return load( file, null );
} }
@Override @Override
public Configuration load(File file, Configuration defaults) throws IOException public Configuration load(File file, Configuration defaults) throws IOException {
{ try (FileInputStream is = new FileInputStream(file)) {
try ( FileInputStream is = new FileInputStream( file ) ) return load(is, defaults);
{
return load( is, defaults );
} }
} }
@Override @Override
public Configuration load(Reader reader) public Configuration load(Reader reader) {
{ return load(reader, null);
return load( reader, null );
} }
@SneakyThrows @SneakyThrows
@Override @Override
public Configuration load(Reader reader, Configuration defaults) public Configuration load(Reader reader, Configuration defaults) {
{ BufferedReader input =
BufferedReader input = reader instanceof BufferedReader ? (BufferedReader)reader : new BufferedReader(reader); reader instanceof BufferedReader ? (BufferedReader) reader : new BufferedReader(reader);
StringBuilder builder = new StringBuilder(); StringBuilder builder = new StringBuilder();
String line; String line;
try { try {
while((line = input.readLine()) != null) { while ((line = input.readLine()) != null) {
builder.append(line); builder.append(line);
builder.append('\n'); builder.append('\n');
} }
@@ -156,42 +155,35 @@ public class YamlConfiguration extends ConfigurationProvider
input.close(); input.close();
} }
return load(builder.toString(), defaults); return load(builder.toString(), defaults);
} }
@Override @Override
public Configuration load(InputStream is) public Configuration load(InputStream is) {
{
return this.load(new InputStreamReader(is, Charset.defaultCharset())); return this.load(new InputStreamReader(is, Charset.defaultCharset()));
} }
@Override @Override
public Configuration load(InputStream is, Configuration defaults) public Configuration load(InputStream is, Configuration defaults) {
{
return this.load(new InputStreamReader(is, Charset.defaultCharset()), defaults); return this.load(new InputStreamReader(is, Charset.defaultCharset()), defaults);
} }
@Override @Override
public Configuration load(String string) public Configuration load(String string) {
{ return load(string, null);
return load( string, null );
} }
@Override @Override
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public Configuration load(String contents, Configuration defaults) public Configuration load(String contents, Configuration defaults) {
{
Map<String, Object> map; Map<String, Object> map;
LoaderOptions loaderOptions = new LoaderOptions(); LoaderOptions loaderOptions = new LoaderOptions();
loaderOptions.setMaxAliasesForCollections(2147483647); loaderOptions.setMaxAliasesForCollections(2147483647);
map = this.yaml.get().loadAs(contents, LinkedHashMap.class); map = this.yaml.get().loadAs(contents, LinkedHashMap.class);
Configuration config = new Configuration( map, defaults ); Configuration config = new Configuration(map, defaults);
config.loadFromString(contents); config.loadFromString(contents);
return config; return config;
} }
} }
@@ -17,21 +17,18 @@
package dev.brighten.antivpn.utils.json; package dev.brighten.antivpn.utils.json;
/** /**
* This provides static methods to convert comma delimited text into a * This provides static methods to convert comma delimited text into a JSONArray, and to covert a
* JSONArray, and to covert a JSONArray into comma delimited text. Comma * JSONArray into comma delimited text. Comma delimited text is a very popular format for data
* delimited text is a very popular format for data interchange. It is * interchange. It is understood by most database, spreadsheet, and organizer programs.
* understood by most database, spreadsheet, and organizer programs. *
* <p> * <p>Each row of text represents a row in a table or a data record. Each row ends with a NEWLINE
* Each row of text represents a row in a table or a data record. Each row * character. Each row contains one or more values. Values are separated by commas. A value can
* ends with a NEWLINE character. Each row contains one or more values. * contain any character except for comma, unless is is wrapped in single quotes or double quotes.
* Values are separated by commas. A value can contain any character except *
* for comma, unless is is wrapped in single quotes or double quotes. * <p>The first row usually contains the names of the columns.
* <p> *
* The first row usually contains the names of the columns. * <p>A comma delimited list can be converted into a JSONArray of JSONObjects. The names for the
* <p> * elements in the JSONObjects can be taken from the names in the first row.
* 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
@@ -39,8 +36,7 @@ package dev.brighten.antivpn.utils.json;
public class CDL { public class CDL {
/** /**
* Get the next value. The value can be wrapped in quotes. The value can * Get the next value. The value can be wrapped in quotes. The value can be empty.
* be empty.
* *
* @param x A JSONTokener of the source text. * @param x A JSONTokener of the source text.
* @return The value string, or null if empty. * @return The value string, or null if empty.
@@ -92,8 +88,7 @@ public class CDL {
for (; ; ) { for (; ; ) {
String value = getValue(x); String value = getValue(x);
char c = x.next(); char c = x.next();
if (value == null || if (value == null || (ja.length() == 0 && value.isEmpty() && c != ',')) {
(ja.length() == 0 && value.length() == 0 && c != ',')) {
return null; return null;
} }
ja.put(value); ja.put(value);
@@ -105,8 +100,7 @@ public class CDL {
if (c == '\n' || c == '\r' || c == 0) { if (c == '\n' || c == '\r' || c == 0) {
return ja; return ja;
} }
throw x.syntaxError("Bad character '" + c + "' (" + throw x.syntaxError("Bad character '" + c + "' (" + (int) c + ").");
(int) c + ").");
} }
c = x.next(); c = x.next();
} }
@@ -114,26 +108,23 @@ public class CDL {
} }
/** /**
* Produce a JSONObject from a row of comma delimited text, using a * Produce a JSONObject from a row of comma delimited text, using a parallel JSONArray of strings
* parallel JSONArray of strings to provides the names of the elements. * to provides the names of the elements.
* *
* @param names A JSONArray of names. This is commonly obtained from the * @param names A JSONArray of names. This is commonly obtained from the first row of a comma
* first row of a comma delimited text file using the rowToJSONArray * delimited text file using the rowToJSONArray method.
* method.
* @param x A JSONTokener of the source text. * @param x A JSONTokener of the source text.
* @return A JSONObject combining the names and values. * @return A JSONObject combining the names and values.
* @throws JSONException * @throws JSONException
*/ */
public static JSONObject rowToJSONObject(JSONArray names, JSONTokener x) public static JSONObject rowToJSONObject(JSONArray names, JSONTokener x) throws JSONException {
throws JSONException {
JSONArray ja = rowToJSONArray(x); JSONArray ja = rowToJSONArray(x);
return ja != null ? ja.toJSONObject(names) : null; return ja != null ? ja.toJSONObject(names) : null;
} }
/** /**
* Produce a comma delimited text row from a JSONArray. Values containing * Produce a comma delimited text row from a JSONArray. Values containing the comma character will
* the comma character will be quoted. Troublesome characters may be * be quoted. Troublesome characters may be removed.
* removed.
* *
* @param ja A JSONArray of strings. * @param ja A JSONArray of strings.
* @return A string ending in NEWLINE. * @return A string ending in NEWLINE.
@@ -147,9 +138,12 @@ public class CDL {
Object object = ja.opt(i); Object object = ja.opt(i);
if (object != null) { if (object != null) {
String string = object.toString(); String string = object.toString();
if (string.length() > 0 && (string.indexOf(',') >= 0 || if (string.length() > 0
string.indexOf('\n') >= 0 || string.indexOf('\r') >= 0 || && (string.indexOf(',') >= 0
string.indexOf(0) >= 0 || string.charAt(0) == '"')) { || string.indexOf('\n') >= 0
|| string.indexOf('\r') >= 0
|| string.indexOf(0) >= 0
|| string.charAt(0) == '"')) {
sb.append('"'); sb.append('"');
int length = string.length(); int length = string.length();
for (int j = 0; j < length; j += 1) { for (int j = 0; j < length; j += 1) {
@@ -169,8 +163,8 @@ public class CDL {
} }
/** /**
* Produce a JSONArray of JSONObjects from a comma delimited text string, * Produce a JSONArray of JSONObjects from a comma delimited text string, using the first row as a
* using the first row as a source of names. * source of names.
* *
* @param string The comma delimited text. * @param string The comma delimited text.
* @return A JSONArray of JSONObjects. * @return A JSONArray of JSONObjects.
@@ -181,8 +175,8 @@ public class CDL {
} }
/** /**
* Produce a JSONArray of JSONObjects from a comma delimited text string, * Produce a JSONArray of JSONObjects from a comma delimited text string, using the first row as a
* using the first row as a source of names. * source of names.
* *
* @param x The JSONTokener containing the comma delimited text. * @param x The JSONTokener containing the comma delimited text.
* @return A JSONArray of JSONObjects. * @return A JSONArray of JSONObjects.
@@ -193,30 +187,28 @@ public class CDL {
} }
/** /**
* Produce a JSONArray of JSONObjects from a comma delimited text string * Produce a JSONArray of JSONObjects from a comma delimited text string using a supplied
* using a supplied JSONArray as the source of element names. * JSONArray as the source of element names.
* *
* @param names A JSONArray of strings. * @param names A JSONArray of strings.
* @param string The comma delimited text. * @param string The comma delimited text.
* @return A JSONArray of JSONObjects. * @return A JSONArray of JSONObjects.
* @throws JSONException * @throws JSONException
*/ */
public static JSONArray toJSONArray(JSONArray names, String string) public static JSONArray toJSONArray(JSONArray names, String string) throws JSONException {
throws JSONException {
return toJSONArray(names, new JSONTokener(string)); return toJSONArray(names, new JSONTokener(string));
} }
/** /**
* Produce a JSONArray of JSONObjects from a comma delimited text string * Produce a JSONArray of JSONObjects from a comma delimited text string using a supplied
* using a supplied JSONArray as the source of element names. * JSONArray as the source of element names.
* *
* @param names A JSONArray of strings. * @param names A JSONArray of strings.
* @param x A JSONTokener of the source text. * @param x A JSONTokener of the source text.
* @return A JSONArray of JSONObjects. * @return A JSONArray of JSONObjects.
* @throws JSONException * @throws JSONException
*/ */
public static JSONArray toJSONArray(JSONArray names, JSONTokener x) public static JSONArray toJSONArray(JSONArray names, JSONTokener x) throws JSONException {
throws JSONException {
if (names == null || names.length() == 0) { if (names == null || names.length() == 0) {
return null; return null;
} }
@@ -234,11 +226,9 @@ public class CDL {
return ja; return ja;
} }
/** /**
* Produce a comma delimited text from a JSONArray of JSONObjects. The * Produce a comma delimited text from a JSONArray of JSONObjects. The first row will be a list of
* first row will be a list of names obtained by inspecting the first * names obtained by inspecting the first JSONObject.
* JSONObject.
* *
* @param ja A JSONArray of JSONObjects. * @param ja A JSONArray of JSONObjects.
* @return A comma delimited text. * @return A comma delimited text.
@@ -256,17 +246,15 @@ public class CDL {
} }
/** /**
* Produce a comma delimited text from a JSONArray of JSONObjects using * Produce a comma delimited text from a JSONArray of JSONObjects using a provided list of names.
* a provided list of names. The list of names is not included in the * The list of names is not included in the output.
* output.
* *
* @param names A JSONArray of strings. * @param names A JSONArray of strings.
* @param ja A JSONArray of JSONObjects. * @param ja A JSONArray of JSONObjects.
* @return A comma delimited text. * @return A comma delimited text.
* @throws JSONException * @throws JSONException
*/ */
public static String toString(JSONArray names, JSONArray ja) public static String toString(JSONArray names, JSONArray ja) throws JSONException {
throws JSONException {
if (names == null || names.length() == 0) { if (names == null || names.length() == 0) {
return null; return null;
} }
@@ -17,8 +17,8 @@
package dev.brighten.antivpn.utils.json; package dev.brighten.antivpn.utils.json;
/** /**
* Convert a web browser cookie specification to a JSONObject and back. * Convert a web browser cookie specification to a JSONObject and back. JSON and Cookies are both
* JSON and Cookies are both notations for name/value pairs. * notations for name/value pairs.
* *
* @author JSON.org * @author JSON.org
* @version 2010-12-24 * @version 2010-12-24
@@ -26,14 +26,12 @@ package dev.brighten.antivpn.utils.json;
public class Cookie { public class Cookie {
/** /**
* Produce a copy of a string in which the characters '+', '%', '=', ';' * Produce a copy of a string in which the characters '+', '%', '=', ';' and control characters
* and control characters are replaced with "%hh". This is a gentle form * are replaced with "%hh". This is a gentle form of URL encoding, attempting to cause as little
* of URL encoding, attempting to cause as little distortion to the * distortion to the string as possible. The characters '=' and ';' are meta characters in
* string as possible. The characters '=' and ';' are meta characters in * cookies. By convention, they are escaped using the URL-encoding. This is only a convention, not
* cookies. By convention, they are escaped using the URL-encoding. This is * a standard. Often, cookies are expected to have encoded values. We encode '=' and ';' because
* only a convention, not a standard. Often, cookies are expected to have * we must. We encode '%' and '+' because they are meta characters in URL encoding.
* encoded values. We encode '=' and ';' because we must. We encode '%' and
* '+' because they are meta characters in URL encoding.
* *
* @param string The source string. * @param string The source string.
* @return The escaped result. * @return The escaped result.
@@ -56,21 +54,17 @@ public class Cookie {
return sb.toString(); return sb.toString();
} }
/** /**
* Convert a cookie specification string into a JSONObject. The string * Convert a cookie specification string into a JSONObject. The string will contain a name value
* will contain a name value pair separated by '='. The name and the value * pair separated by '='. The name and the value will be unescaped, possibly converting '+' and
* will be unescaped, possibly converting '+' and '%' sequences. The * '%' sequences. The cookie properties may follow, separated by ';', also represented as
* 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
* name=value (except the secure property, which does not have a value). * under the key "name", and the value will be stored under the key "value". This method does not
* The name will be stored under the key "name", and the value will be * do checking or validation of the parameters. It only converts the cookie string into a
* stored under the key "value". This method does not do checking or * JSONObject.
* validation of the parameters. It only converts the cookie string into
* a JSONObject.
* *
* @param string The cookie specification string. * @param string The cookie specification string.
* @return A JSONObject containing "name", "value", and possibly other * @return A JSONObject containing "name", "value", and possibly other members.
* members.
* @throws JSONException * @throws JSONException
*/ */
public static JSONObject toJSONObject(String string) throws JSONException { public static JSONObject toJSONObject(String string) throws JSONException {
@@ -99,13 +93,10 @@ public class Cookie {
return jo; return jo;
} }
/** /**
* Convert a JSONObject into a cookie specification string. The JSONObject * Convert a JSONObject into a cookie specification string. The JSONObject must contain "name" and
* must contain "name" and "value" members. * "value" members. If the JSONObject contains "expires", "domain", "path", or "secure" members,
* If the JSONObject contains "expires", "domain", "path", or "secure" * they will be appended to the cookie specification string. All other members are ignored.
* members, they will be appended to the cookie specification string.
* All other members are ignored.
* *
* @param jo A JSONObject * @param jo A JSONObject
* @return A cookie specification string * @return A cookie specification string
@@ -136,12 +127,10 @@ public class Cookie {
} }
/** /**
* Convert <code>%</code><i>hh</i> sequences to single characters, and * Convert <code>%</code><i>hh</i> sequences to single characters, and convert plus to space.
* convert plus to space.
* *
* @param string A string that may contain * @param string A string that may contain <code>+</code>&nbsp;<small>(plus)</small> and <code>%
* <code>+</code>&nbsp;<small>(plus)</small> and * </code><i>hh</i> sequences.
* <code>%</code><i>hh</i> sequences.
* @return The unescaped string. * @return The unescaped string.
*/ */
public static String unescape(String string) { public static String unescape(String string) {
@@ -27,13 +27,11 @@ import java.util.Iterator;
public class CookieList { public class CookieList {
/** /**
* Convert a cookie list into a JSONObject. A cookie list is a sequence * Convert a cookie list into a JSONObject. A cookie list is a sequence of name/value pairs. The
* of name/value pairs. The names are separated from the values by '='. * names are separated from the values by '='. The pairs are separated by ';'. The names and the
* The pairs are separated by ';'. The names and the values * values will be unescaped, possibly converting '+' and '%' sequences.
* will be unescaped, possibly converting '+' and '%' sequences. *
* <p> * <p>To add a cookie to a cooklist, cookielistJSONObject.put(cookieJSONObject.getString("name"),
* To add a cookie to a cooklist,
* cookielistJSONObject.put(cookieJSONObject.getString("name"),
* cookieJSONObject.getString("value")); * cookieJSONObject.getString("value"));
* *
* @param string A cookie list string * @param string A cookie list string
@@ -52,12 +50,10 @@ public class CookieList {
return jo; return jo;
} }
/** /**
* Convert a JSONObject into a cookie list. A cookie list is a sequence * Convert a JSONObject into a cookie list. A cookie list is a sequence of name/value pairs. The
* of name/value pairs. The names are separated from the values by '='. * names are separated from the values by '='. The pairs are separated by ';'. The characters '%',
* The pairs are separated by ';'. The characters '%', '+', '=', and ';' * '+', '=', and ';' in the names and values are replaced by "%hh".
* in the names and values are replaced by "%hh".
* *
* @param jo A JSONObject * @param jo A JSONObject
* @return A cookie list string * @return A cookie list string
@@ -26,42 +26,48 @@ import java.util.Iterator;
*/ */
public class HTTP { public class HTTP {
/** /** Carriage return/line feed. */
* Carriage return/line feed.
*/
public static final String CRLF = "\r\n"; public static final String CRLF = "\r\n";
/** /**
* Convert an HTTP header string into a JSONObject. It can be a request * Convert an HTTP header string into a JSONObject. It can be a request header or a response
* header or a response header. A request header will contain * 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 * A response header will contain
*
* <pre>{ * <pre>{
* "HTTP-Version": "HTTP/1.1" (for example), * "HTTP-Version": "HTTP/1.1" (for example),
* "Fixes-Code": "200" (for example), * "Fixes-Code": "200" (for example),
* "Reason-Phrase": "OK" (for example) * "Reason-Phrase": "OK" (for example)
* }</pre> * }</pre>
* In addition, the other parameters in the header will be captured, using *
* the HTTP field names as JSON names, so that <pre> * In addition, the other parameters in the header will be captured, using the HTTP field names as
* JSON names, so that
*
* <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</pre> * Cache-Control: no-cache</pre>
*
* become * 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. *
* It does not do '%' transforms on URLs. * It does no further checking or conversion. It does not parse dates. It does not do '%'
* transforms on URLs.
* *
* @param string An HTTP header string. * @param string An HTTP header string.
* @return A JSONObject containing the elements and attributes * @return A JSONObject containing the elements and attributes of the XML string.
* of the XML string.
* @throws JSONException * @throws JSONException
*/ */
public static JSONObject toJSONObject(String string) throws JSONException { public static JSONObject toJSONObject(String string) throws JSONException {
@@ -72,7 +78,7 @@ public class HTTP {
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());
@@ -81,14 +87,14 @@ public class HTTP {
} 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 // Fields
while (x.more()) { while (x.more()) {
String name = x.nextTo(':'); String name = x.nextTo(':');
@@ -99,27 +105,29 @@ public class HTTP {
return jo; return jo;
} }
/** /**
* Convert a JSONObject into an HTTP header. A request header must contain * Convert a JSONObject into an HTTP header. A request header must 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 must contain * A response header must contain
*
* <pre>{ * <pre>{
* "HTTP-Version": "HTTP/1.1" (for example), * "HTTP-Version": "HTTP/1.1" (for example),
* "Fixes-Code": "200" (for example), * "Fixes-Code": "200" (for example),
* "Reason-Phrase": "OK" (for example) * "Reason-Phrase": "OK" (for example)
* }</pre> * }</pre>
* Any other members of the JSONObject will be output as HTTP fields. *
* The result will end with two CRLF pairs. * Any other members of the JSONObject will be output as HTTP fields. The result will end with two
* CRLF pairs.
* *
* @param jo A JSONObject * @param jo A JSONObject
* @return An HTTP header string. * @return An HTTP header string.
* @throws JSONException if the object does not contain enough * @throws JSONException if the object does not contain enough information.
* information.
*/ */
public static String toString(JSONObject jo) throws JSONException { public static String toString(JSONObject jo) throws JSONException {
Iterator keys = jo.keys(); Iterator keys = jo.keys();
@@ -145,9 +153,12 @@ public class HTTP {
sb.append(CRLF); sb.append(CRLF);
while (keys.hasNext()) { while (keys.hasNext()) {
string = keys.next().toString(); string = keys.next().toString();
if (!string.equals("HTTP-Version") && !string.equals("Fixes-Code") && if (!string.equals("HTTP-Version")
!string.equals("Reason-Phrase") && !string.equals("Method") && && !string.equals("Fixes-Code")
!string.equals("Request-URI") && !jo.isNull(string)) { && !string.equals("Reason-Phrase")
&& !string.equals("Method")
&& !string.equals("Request-URI")
&& !jo.isNull(string)) {
sb.append(string); sb.append(string);
sb.append(": "); sb.append(": ");
sb.append(jo.getString(string)); sb.append(jo.getString(string));
@@ -17,8 +17,8 @@
package dev.brighten.antivpn.utils.json; package dev.brighten.antivpn.utils.json;
/** /**
* The HTTPTokener extends the JSONTokener to provide additional methods * The HTTPTokener extends the JSONTokener to provide additional methods for the parsing of HTTP
* for the parsing of HTTP headers. * headers.
* *
* @author JSON.org * @author JSON.org
* @version 2010-12-24 * @version 2010-12-24
@@ -34,7 +34,6 @@ public class HTTPTokener extends JSONTokener {
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.
* *
@@ -25,48 +25,41 @@ import java.util.Iterator;
import java.util.Map; import java.util.Map;
/** /**
* A JSONArray is an ordered sequence of values. Its external text form is a * A JSONArray is an ordered sequence of values. Its external text form is a string wrapped in
* string wrapped in square brackets with commas separating the values. The * square brackets with commas separating the values. The internal form is an object having <code>
* internal form is an object having <code>get</code> and <code>opt</code> * get</code> and <code>opt</code> methods for accessing the values by index, and <code>put</code>
* methods for accessing the values by index, and <code>put</code> methods for * methods for adding or replacing values. The values can be any of these types: <code>Boolean
* adding or replacing values. The values can be any of these types: * </code>, <code>JSONArray</code>, <code>JSONObject</code>, <code>Number</code>, <code>String
* <code>Boolean</code>, <code>JSONArray</code>, <code>JSONObject</code>, * </code>, or the <code>JSONObject.NULL object</code>.
* <code>Number</code>, <code>String</code>, or the *
* <code>JSONObject.NULL object</code>. * <p>The constructor can convert a JSON text into a Java object. The <code>toString</code> method
* <p> * converts to JSON text.
* The constructor can convert a JSON text into a Java object. The *
* <code>toString</code> method converts to JSON text. * <p>A <code>get</code> method returns a value if one can be found, and throws an exception if one
* <p> * cannot be found. An <code>opt</code> method returns a default value instead of throwing an
* A <code>get</code> method returns a value if one can be found, and throws an * exception, and so is useful for obtaining optional values.
* exception if one cannot be found. An <code>opt</code> method returns a *
* default value instead of throwing an exception, and so is useful for * <p>The generic <code>get()</code> and <code>opt()</code> methods return an object which you can
* obtaining optional values. * cast or query for type. There are also typed <code>get</code> and <code>opt</code> methods that
* <p> * do type checking and type coercion for you.
* The generic <code>get()</code> and <code>opt()</code> methods return an *
* object which you can cast or query for type. There are also typed * <p>The texts produced by the <code>toString</code> methods strictly conform to JSON syntax rules.
* <code>get</code> and <code>opt</code> methods that do type checking and type * The constructors are more forgiving in the texts they will accept:
* coercion for you. *
* <p>
* The texts produced by the <code>toString</code> methods strictly conform to
* JSON syntax rules. The constructors are more forgiving in the texts they will
* accept:
* <ul> * <ul>
* <li>An extra <code>,</code>&nbsp;<small>(comma)</small> may appear just * <li>An extra <code>,</code>&nbsp;<small>(comma)</small> may appear just before the closing
* before the closing bracket.</li> * bracket.
* <li>The <code>null</code> value will be inserted when there * <li>The <code>null</code> value will be inserted when there is <code>,</code>
* is <code>,</code>&nbsp;<small>(comma)</small> elision.</li> * &nbsp;<small>(comma)</small> elision.
* <li>Strings may be quoted with <code>'</code>&nbsp;<small>(single * <li>Strings may be quoted with <code>'</code>&nbsp;<small>(single quote)</small>.
* quote)</small>.</li> * <li>Strings do not need to be quoted at all if they do not begin with a quote or single quote,
* <li>Strings do not need to be quoted at all if they do not begin with a quote * and if they do not contain leading or trailing spaces, and if they do not contain any of
* or single quote, and if they do not contain leading or trailing spaces, * these characters: <code>{ } [ ] / \ : , = ; #</code> and if they do not look like numbers
* and if they do not contain any of these characters: * and if they are not the reserved words <code>true</code>, <code>false</code>, or <code>null
* <code>{ } [ ] / \ : , = ; #</code> and if they do not look like numbers * </code>.
* and if they are not the reserved words <code>true</code>, * <li>Values can be separated by <code>;</code> <small>(semicolon)</small> as well as by <code>,
* <code>false</code>, or <code>null</code>.</li> * </code> <small>(comma)</small>.
* <li>Values can be separated by <code>;</code> <small>(semicolon)</small> as * <li>Numbers may have the <code>0x-</code> <small>(hex)</small> prefix.
* well as by <code>,</code> <small>(comma)</small>.</li>
* <li>Numbers may have the
* <code>0x-</code> <small>(hex)</small> prefix.</li>
* </ul> * </ul>
* *
* @author JSON.org * @author JSON.org
@@ -74,16 +67,10 @@ import java.util.Map;
*/ */
public class JSONArray { public class JSONArray {
/** The arrayList where the JSONArray's properties are kept. */
/**
* The arrayList where the JSONArray's properties are kept.
*/
private ArrayList myArrayList; private ArrayList myArrayList;
/** Construct an empty JSONArray. */
/**
* Construct an empty JSONArray.
*/
public JSONArray() { public JSONArray() {
this.myArrayList = new ArrayList(); this.myArrayList = new ArrayList();
} }
@@ -126,20 +113,17 @@ public class JSONArray {
} }
} }
/** /**
* Construct a JSONArray from a source JSON text. * Construct a JSONArray from a source JSON text.
* *
* @param source A string that begins with * @param source A string that begins with <code>[</code>&nbsp;<small>(left bracket)</small> and
* <code>[</code>&nbsp;<small>(left bracket)</small> * ends with <code>]</code>&nbsp;<small>(right bracket)</small>.
* and ends with <code>]</code>&nbsp;<small>(right bracket)</small>.
* @throws JSONException If there is a syntax error. * @throws JSONException If there is a syntax error.
*/ */
public JSONArray(String source) throws JSONException { public JSONArray(String source) throws JSONException {
this(new JSONTokener(source)); this(new JSONTokener(source));
} }
/** /**
* Construct a JSONArray from a Collection. * Construct a JSONArray from a Collection.
* *
@@ -155,7 +139,6 @@ public class JSONArray {
} }
} }
/** /**
* Construct a JSONArray from an array * Construct a JSONArray from an array
* *
@@ -169,12 +152,10 @@ public class JSONArray {
this.put(JSONObject.wrap(Array.get(array, i))); this.put(JSONObject.wrap(Array.get(array, i)));
} }
} else { } else {
throw new JSONException( throw new JSONException("JSONArray initial value should be a string or collection or array.");
"JSONArray initial value should be a string or collection or array.");
} }
} }
/** /**
* Get the object value associated with an index. * Get the object value associated with an index.
* *
@@ -190,52 +171,45 @@ public class JSONArray {
return object; return object;
} }
/** /**
* Get the boolean value associated with an index. * Get the boolean value associated with an index. The string values "true" and "false" are
* The string values "true" and "false" are converted to boolean. * converted to boolean.
* *
* @param index The index must be between 0 and length() - 1. * @param index The index must be between 0 and length() - 1.
* @return The truth. * @return The truth.
* @throws JSONException If there is no value for the index or if the * @throws JSONException If there is no value for the index or if the value is not convertible to
* value is not convertible to boolean. * boolean.
*/ */
public boolean getBoolean(int index) throws JSONException { public boolean getBoolean(int index) throws JSONException {
Object object = get(index); Object object = get(index);
if (object.equals(Boolean.FALSE) || if (object.equals(Boolean.FALSE)
(object instanceof String && || (object instanceof String && ((String) object).equalsIgnoreCase("false"))) {
((String) object).equalsIgnoreCase("false"))) {
return false; return false;
} else if (object.equals(Boolean.TRUE) || } else if (object.equals(Boolean.TRUE)
(object instanceof String && || (object instanceof String && ((String) object).equalsIgnoreCase("true"))) {
((String) object).equalsIgnoreCase("true"))) {
return true; return true;
} }
throw new JSONException("JSONArray[" + index + "] is not a boolean."); throw new JSONException("JSONArray[" + index + "] is not a boolean.");
} }
/** /**
* Get the double value associated with an index. * Get the double value associated with an index.
* *
* @param index The index must be between 0 and length() - 1. * @param index The index must be between 0 and length() - 1.
* @return The value. * @return The value.
* @throws JSONException If the key is not found or if the value cannot * @throws JSONException If the key is not found or if the value cannot be converted to a number.
* be converted to a number.
*/ */
public double getDouble(int index) throws JSONException { public double getDouble(int index) throws JSONException {
Object object = get(index); Object object = get(index);
try { try {
return object instanceof Number ? return object instanceof Number
((Number) object).doubleValue() : ? ((Number) object).doubleValue()
Double.parseDouble((String) object); : Double.parseDouble((String) object);
} catch (Exception e) { } catch (Exception e) {
throw new JSONException("JSONArray[" + index + throw new JSONException("JSONArray[" + index + "] is not a number.");
"] is not a number.");
} }
} }
/** /**
* Get the int value associated with an index. * Get the int value associated with an index.
* *
@@ -246,73 +220,62 @@ public class JSONArray {
public int getInt(int index) throws JSONException { public int getInt(int index) throws JSONException {
Object object = get(index); Object object = get(index);
try { try {
return object instanceof Number ? return object instanceof Number
((Number) object).intValue() : ? ((Number) object).intValue()
Integer.parseInt((String) object); : Integer.parseInt((String) object);
} catch (Exception e) { } catch (Exception e) {
throw new JSONException("JSONArray[" + index + throw new JSONException("JSONArray[" + index + "] is not a number.");
"] is not a number.");
} }
} }
/** /**
* Get the JSONArray associated with an index. * Get the JSONArray associated with an index.
* *
* @param index The index must be between 0 and length() - 1. * @param index The index must be between 0 and length() - 1.
* @return A JSONArray value. * @return A JSONArray value.
* @throws JSONException If there is no value for the index. or if the * @throws JSONException If there is no value for the index. or if the value is not a JSONArray
* value is not a JSONArray
*/ */
public JSONArray getJSONArray(int index) throws JSONException { public JSONArray getJSONArray(int index) throws JSONException {
Object object = get(index); Object object = get(index);
if (object instanceof JSONArray) { if (object instanceof JSONArray) {
return (JSONArray) object; return (JSONArray) object;
} }
throw new JSONException("JSONArray[" + index + throw new JSONException("JSONArray[" + index + "] is not a JSONArray.");
"] is not a JSONArray.");
} }
/** /**
* Get the JSONObject associated with an index. * Get the JSONObject associated with an index.
* *
* @param index subscript * @param index subscript
* @return A JSONObject value. * @return A JSONObject value.
* @throws JSONException If there is no value for the index or if the * @throws JSONException If there is no value for the index or if the value is not a JSONObject
* value is not a JSONObject
*/ */
public JSONObject getJSONObject(int index) throws JSONException { public JSONObject getJSONObject(int index) throws JSONException {
Object object = get(index); Object object = get(index);
if (object instanceof JSONObject) { if (object instanceof JSONObject) {
return (JSONObject) object; return (JSONObject) object;
} }
throw new JSONException("JSONArray[" + index + throw new JSONException("JSONArray[" + index + "] is not a JSONObject.");
"] is not a JSONObject.");
} }
/** /**
* Get the long value associated with an index. * Get the long value associated with an index.
* *
* @param index The index must be between 0 and length() - 1. * @param index The index must be between 0 and length() - 1.
* @return The value. * @return The value.
* @throws JSONException If the key is not found or if the value cannot * @throws JSONException If the key is not found or if the value cannot be converted to a number.
* be converted to a number.
*/ */
public long getLong(int index) throws JSONException { public long getLong(int index) throws JSONException {
Object object = get(index); Object object = get(index);
try { try {
return object instanceof Number ? return object instanceof Number
((Number) object).longValue() : ? ((Number) object).longValue()
Long.parseLong((String) object); : Long.parseLong((String) object);
} catch (Exception e) { } catch (Exception e) {
throw new JSONException("JSONArray[" + index + throw new JSONException("JSONArray[" + index + "] is not a number.");
"] is not a number.");
} }
} }
/** /**
* Get the string associated with an index. * Get the string associated with an index.
* *
@@ -328,7 +291,6 @@ public class JSONArray {
throw new JSONException("JSONArray[" + index + "] not a string."); throw new JSONException("JSONArray[" + index + "] not a string.");
} }
/** /**
* Determine if the value is null. * Determine if the value is null.
* *
@@ -339,11 +301,10 @@ public class JSONArray {
return JSONObject.NULL.equals(opt(index)); return JSONObject.NULL.equals(opt(index));
} }
/** /**
* Make a string from the contents of this JSONArray. The * Make a string from the contents of this JSONArray. The <code>separator</code> string is
* <code>separator</code> string is inserted between each element. * inserted between each element. Warning: This method assumes that the data structure is
* Warning: This method assumes that the data structure is acyclical. * acyclical.
* *
* @param separator A string that will be inserted between the elements. * @param separator A string that will be inserted between the elements.
* @return a string. * @return a string.
@@ -362,7 +323,6 @@ public class JSONArray {
return sb.toString(); return sb.toString();
} }
/** /**
* Get the number of elements in the JSONArray, included nulls. * Get the number of elements in the JSONArray, included nulls.
* *
@@ -372,24 +332,19 @@ public class JSONArray {
return this.myArrayList.size(); return this.myArrayList.size();
} }
/** /**
* Get the optional object value associated with an index. * Get the optional object value associated with an index.
* *
* @param index The index must be between 0 and length() - 1. * @param index The index must be between 0 and length() - 1.
* @return An object value, or null if there is no * @return An object value, or null if there is no object at that index.
* object at that index.
*/ */
public Object opt(int index) { public Object opt(int index) {
return (index < 0 || index >= length()) ? return (index < 0 || index >= length()) ? null : this.myArrayList.get(index);
null : this.myArrayList.get(index);
} }
/** /**
* Get the optional boolean value associated with an index. * Get the optional boolean value associated with an index. It returns false if there is no value
* It returns false if there is no value at that index, * at that index, or if the value is not Boolean.TRUE or the String "true".
* or if the value is not Boolean.TRUE or the String "true".
* *
* @param index The index must be between 0 and length() - 1. * @param index The index must be between 0 and length() - 1.
* @return The truth. * @return The truth.
@@ -398,11 +353,10 @@ public class JSONArray {
return optBoolean(index, false); return optBoolean(index, false);
} }
/** /**
* Get the optional boolean value associated with an index. * Get the optional boolean value associated with an index. It returns the defaultValue if there
* It returns the defaultValue if there is no value at that index or if * is no value at that index or if it is not a Boolean or the String "true" or "false" (case
* it is not a Boolean or the String "true" or "false" (case insensitive). * insensitive).
* *
* @param index The index must be between 0 and length() - 1. * @param index The index must be between 0 and length() - 1.
* @param defaultValue A boolean default. * @param defaultValue A boolean default.
@@ -416,11 +370,9 @@ public class JSONArray {
} }
} }
/** /**
* Get the optional double value associated with an index. * Get the optional double value associated with an index. NaN is returned if there is no value
* NaN is returned if there is no value for the index, * for the index, or if the value is not a number and cannot be converted to a number.
* or if the value is not a number and cannot be converted to a number.
* *
* @param index The index must be between 0 and length() - 1. * @param index The index must be between 0 and length() - 1.
* @return The value. * @return The value.
@@ -429,11 +381,9 @@ public class JSONArray {
return optDouble(index, Double.NaN); return optDouble(index, Double.NaN);
} }
/** /**
* Get the optional double value associated with an index. * Get the optional double value associated with an index. The defaultValue is returned if there
* The defaultValue is returned if there is no value for the index, * is no value for the index, or if the value is not a number and cannot be converted to a number.
* or if the value is not a number and cannot be converted to a number.
* *
* @param index subscript * @param index subscript
* @param defaultValue The default value. * @param defaultValue The default value.
@@ -447,11 +397,9 @@ public class JSONArray {
} }
} }
/** /**
* Get the optional int value associated with an index. * Get the optional int value associated with an index. Zero is returned if there is no value for
* Zero is returned if there is no value for the index, * the index, or if the value is not a number and cannot be converted to a number.
* or if the value is not a number and cannot be converted to a number.
* *
* @param index The index must be between 0 and length() - 1. * @param index The index must be between 0 and length() - 1.
* @return The value. * @return The value.
@@ -460,11 +408,9 @@ public class JSONArray {
return optInt(index, 0); return optInt(index, 0);
} }
/** /**
* Get the optional int value associated with an index. * Get the optional int value associated with an index. The defaultValue is returned if there is
* The defaultValue is returned if there is no value for the index, * no value for the index, or if the value is not a number and cannot be converted to a number.
* or if the value is not a number and cannot be converted to a number.
* *
* @param index The index must be between 0 and length() - 1. * @param index The index must be between 0 and length() - 1.
* @param defaultValue The default value. * @param defaultValue The default value.
@@ -478,24 +424,21 @@ public class JSONArray {
} }
} }
/** /**
* Get the optional JSONArray associated with an index. * Get the optional JSONArray associated with an index.
* *
* @param index subscript * @param index subscript
* @return A JSONArray value, or null if the index has no value, * @return A JSONArray value, or null if the index has no value, or if the value is not a
* or if the value is not a JSONArray. * JSONArray.
*/ */
public JSONArray optJSONArray(int index) { public JSONArray optJSONArray(int index) {
Object o = opt(index); Object o = opt(index);
return o instanceof JSONArray ? (JSONArray) o : null; return o instanceof JSONArray ? (JSONArray) o : null;
} }
/** /**
* Get the optional JSONObject associated with an index. * Get the optional JSONObject associated with an index. Null is returned if the key is not found,
* Null is returned if the key is not found, or null if the index has * or null if the index has no value, or if the value is not a JSONObject.
* no value, or if the value is not a JSONObject.
* *
* @param index The index must be between 0 and length() - 1. * @param index The index must be between 0 and length() - 1.
* @return A JSONObject value. * @return A JSONObject value.
@@ -505,11 +448,9 @@ public class JSONArray {
return o instanceof JSONObject ? (JSONObject) o : null; return o instanceof JSONObject ? (JSONObject) o : null;
} }
/** /**
* Get the optional long value associated with an index. * Get the optional long value associated with an index. Zero is returned if there is no value for
* Zero is returned if there is no value for the index, * the index, or if the value is not a number and cannot be converted to a number.
* or if the value is not a number and cannot be converted to a number.
* *
* @param index The index must be between 0 and length() - 1. * @param index The index must be between 0 and length() - 1.
* @return The value. * @return The value.
@@ -518,11 +459,9 @@ public class JSONArray {
return optLong(index, 0); return optLong(index, 0);
} }
/** /**
* Get the optional long value associated with an index. * Get the optional long value associated with an index. The defaultValue is returned if there is
* The defaultValue is returned if there is no value for the index, * no value for the index, or if the value is not a number and cannot be converted to a number.
* or if the value is not a number and cannot be converted to a number.
* *
* @param index The index must be between 0 and length() - 1. * @param index The index must be between 0 and length() - 1.
* @param defaultValue The default value. * @param defaultValue The default value.
@@ -536,11 +475,10 @@ public class JSONArray {
} }
} }
/** /**
* Get the optional string value associated with an index. It returns an * Get the optional string value associated with an index. It returns an empty string if there is
* empty string if there is no value at that index. If the value * no value at that index. If the value is not a string and is not null, then it is coverted to a
* is not a string and is not null, then it is coverted to a string. * string.
* *
* @param index The index must be between 0 and length() - 1. * @param index The index must be between 0 and length() - 1.
* @return A String value. * @return A String value.
@@ -549,10 +487,9 @@ public class JSONArray {
return optString(index, ""); return optString(index, "");
} }
/** /**
* Get the optional string associated with an index. * Get the optional string associated with an index. The defaultValue is returned if the key is
* The defaultValue is returned if the key is not found. * not found.
* *
* @param index The index must be between 0 and length() - 1. * @param index The index must be between 0 and length() - 1.
* @param defaultValue The default value. * @param defaultValue The default value.
@@ -563,7 +500,6 @@ public class JSONArray {
return object != null ? object.toString() : defaultValue; return object != null ? object.toString() : defaultValue;
} }
/** /**
* Append a boolean value. This increases the array's length by one. * Append a boolean value. This increases the array's length by one.
* *
@@ -575,10 +511,9 @@ public class JSONArray {
return this; return this;
} }
/** /**
* Put a value in the JSONArray, where the value will be a * Put a value in the JSONArray, where the value will be a JSONArray which is produced from a
* JSONArray which is produced from a Collection. * Collection.
* *
* @param value A Collection value. * @param value A Collection value.
* @return this. * @return this.
@@ -588,7 +523,6 @@ public class JSONArray {
return this; return this;
} }
/** /**
* Append a double value. This increases the array's length by one. * Append a double value. This increases the array's length by one.
* *
@@ -603,7 +537,6 @@ public class JSONArray {
return this; return this;
} }
/** /**
* Append an int value. This increases the array's length by one. * Append an int value. This increases the array's length by one.
* *
@@ -615,7 +548,6 @@ public class JSONArray {
return this; return this;
} }
/** /**
* Append an long value. This increases the array's length by one. * Append an long value. This increases the array's length by one.
* *
@@ -627,10 +559,9 @@ public class JSONArray {
return this; return this;
} }
/** /**
* Put a value in the JSONArray, where the value will be a * Put a value in the JSONArray, where the value will be a JSONObject which is produced from a
* JSONObject which is produced from a Map. * Map.
* *
* @param value A Map value. * @param value A Map value.
* @return this. * @return this.
@@ -640,13 +571,11 @@ public class JSONArray {
return this; return this;
} }
/** /**
* Append an object value. This increases the array's length by one. * Append an object value. This increases the array's length by one.
* *
* @param value An object value. The value should be a * @param value An object value. The value should be a Boolean, Double, Integer, JSONArray,
* Boolean, Double, Integer, JSONArray, JSONObject, Long, or String, or the * JSONObject, Long, or String, or the JSONObject.NULL object.
* JSONObject.NULL object.
* @return this. * @return this.
*/ */
public JSONArray put(Object value) { public JSONArray put(Object value) {
@@ -654,11 +583,9 @@ public class JSONArray {
return this; return this;
} }
/** /**
* Put or replace a boolean value in the JSONArray. If the index is greater * Put or replace a boolean value in the JSONArray. If the index is greater than the length of the
* than the length of the JSONArray, then null elements will be added as * JSONArray, then null elements will be added as necessary to pad it out.
* necessary to pad it out.
* *
* @param index The subscript. * @param index The subscript.
* @param value A boolean value. * @param value A boolean value.
@@ -670,44 +597,37 @@ public class JSONArray {
return this; return this;
} }
/** /**
* Put a value in the JSONArray, where the value will be a * Put a value in the JSONArray, where the value will be a JSONArray which is produced from a
* JSONArray which is produced from a Collection. * Collection.
* *
* @param index The subscript. * @param index The subscript.
* @param value A Collection value. * @param value A Collection value.
* @return this. * @return this.
* @throws JSONException If the index is negative or if the value is * @throws JSONException If the index is negative or if the value is not finite.
* not finite.
*/ */
public JSONArray put(int index, Collection value) throws JSONException { public JSONArray put(int index, Collection value) throws JSONException {
put(index, new JSONArray(value)); put(index, new JSONArray(value));
return this; return this;
} }
/** /**
* Put or replace a double value. If the index is greater than the length of * Put or replace a double value. If the index is greater than the length of the JSONArray, then
* the JSONArray, then null elements will be added as necessary to pad * null elements will be added as necessary to pad it out.
* it out.
* *
* @param index The subscript. * @param index The subscript.
* @param value A double value. * @param value A double value.
* @return this. * @return this.
* @throws JSONException If the index is negative or if the value is * @throws JSONException If the index is negative or if the value is not finite.
* not finite.
*/ */
public JSONArray put(int index, double value) throws JSONException { public JSONArray put(int index, double value) throws JSONException {
put(index, new Double(value)); put(index, new Double(value));
return this; return this;
} }
/** /**
* Put or replace an int value. If the index is greater than the length of * Put or replace an int value. If the index is greater than the length of the JSONArray, then
* the JSONArray, then null elements will be added as necessary to pad * null elements will be added as necessary to pad it out.
* it out.
* *
* @param index The subscript. * @param index The subscript.
* @param value An int value. * @param value An int value.
@@ -719,11 +639,9 @@ public class JSONArray {
return this; return this;
} }
/** /**
* Put or replace a long value. If the index is greater than the length of * Put or replace a long value. If the index is greater than the length of the JSONArray, then
* the JSONArray, then null elements will be added as necessary to pad * null elements will be added as necessary to pad it out.
* it out.
* *
* @param index The subscript. * @param index The subscript.
* @param value A long value. * @param value A long value.
@@ -735,35 +653,28 @@ public class JSONArray {
return this; return this;
} }
/** /**
* Put a value in the JSONArray, where the value will be a * Put a value in the JSONArray, where the value will be a JSONObject that is produced from a Map.
* JSONObject that is produced from a Map.
* *
* @param index The subscript. * @param index The subscript.
* @param value The Map value. * @param value The Map value.
* @return this. * @return this.
* @throws JSONException If the index is negative or if the the value is * @throws JSONException If the index is negative or if the the value is an invalid number.
* an invalid number.
*/ */
public JSONArray put(int index, Map value) throws JSONException { public JSONArray put(int index, Map value) throws JSONException {
put(index, new JSONObject(value)); put(index, new JSONObject(value));
return this; return this;
} }
/** /**
* Put or replace an object value in the JSONArray. If the index is greater * Put or replace an object value in the JSONArray. If the index is greater than the length of the
* than the length of the JSONArray, then null elements will be added as * JSONArray, then null elements will be added as necessary to pad it out.
* necessary to pad it out.
* *
* @param index The subscript. * @param index The subscript.
* @param value The value to put into the array. The value should be a * @param value The value to put into the array. The value should be a Boolean, Double, Integer,
* Boolean, Double, Integer, JSONArray, JSONObject, Long, or String, or the * JSONArray, JSONObject, Long, or String, or the JSONObject.NULL object.
* JSONObject.NULL object.
* @return this. * @return this.
* @throws JSONException If the index is negative or if the the value is * @throws JSONException If the index is negative or if the the value is an invalid number.
* an invalid number.
*/ */
public JSONArray put(int index, Object value) throws JSONException { public JSONArray put(int index, Object value) throws JSONException {
JSONObject.testValidity(value); JSONObject.testValidity(value);
@@ -781,13 +692,11 @@ public class JSONArray {
return this; return this;
} }
/** /**
* Remove an index and close the hole. * Remove an index and close the hole.
* *
* @param index The index of the element to be removed. * @param index The index of the element to be removed.
* @return The value that was associated with the index, * @return The value that was associated with the index, or null if there was no value.
* or null if there was no value.
*/ */
public Object remove(int index) { public Object remove(int index) {
Object o = opt(index); Object o = opt(index);
@@ -795,15 +704,12 @@ public class JSONArray {
return o; return o;
} }
/** /**
* Produce a JSONObject by combining a JSONArray of names with the values * Produce a JSONObject by combining a JSONArray of names with the values of this JSONArray.
* of this JSONArray.
* *
* @param names A JSONArray containing a list of key strings. These will be * @param names A JSONArray containing a list of key strings. These will be paired with the
* paired with the values. * values.
* @return A JSONObject, or null if there are no names or if this JSONArray * @return A JSONObject, or null if there are no names or if this JSONArray has no values.
* has no values.
* @throws JSONException If any of the names are null. * @throws JSONException If any of the names are null.
*/ */
public JSONObject toJSONObject(JSONArray names) throws JSONException { public JSONObject toJSONObject(JSONArray names) throws JSONException {
@@ -817,17 +723,14 @@ public class JSONArray {
return jo; return jo;
} }
/** /**
* Make a JSON text of this JSONArray. For compactness, no * Make a JSON text of this JSONArray. For compactness, no unnecessary whitespace is added. If it
* unnecessary whitespace is added. If it is not possible to produce a * is not possible to produce a syntactically correct JSON text then null will be returned
* syntactically correct JSON text then null will be returned instead. This * instead. This could occur if the array contains an invalid number.
* could occur if the array contains an invalid number.
* <p>
* Warning: This method assumes that the data structure is acyclical.
* *
* @return a printable, displayable, transmittable * <p>Warning: This method assumes that the data structure is acyclical.
* representation of the array. *
* @return a printable, displayable, transmittable representation of the array.
*/ */
public String toString() { public String toString() {
try { try {
@@ -837,33 +740,27 @@ public class JSONArray {
} }
} }
/** /**
* Make a prettyprinted JSON text of this JSONArray. * Make a prettyprinted JSON text of this JSONArray. Warning: This method assumes that the data
* Warning: This method assumes that the data structure is acyclical. * structure is acyclical.
* *
* @param indentFactor The number of spaces to add to each level of * @param indentFactor The number of spaces to add to each level of indentation.
* indentation. * @return a printable, displayable, transmittable representation of the object, beginning with
* @return a printable, displayable, transmittable * <code>[</code>&nbsp;<small>(left bracket)</small> and ending with <code>]</code>
* representation of the object, beginning * &nbsp;<small>(right bracket)</small>.
* with <code>[</code>&nbsp;<small>(left bracket)</small> and ending
* with <code>]</code>&nbsp;<small>(right bracket)</small>.
* @throws JSONException * @throws JSONException
*/ */
public String toString(int indentFactor) throws JSONException { public String toString(int indentFactor) throws JSONException {
return toString(indentFactor, 0); return toString(indentFactor, 0);
} }
/** /**
* Make a prettyprinted JSON text of this JSONArray. * Make a prettyprinted JSON text of this JSONArray. Warning: This method assumes that the data
* Warning: This method assumes that the data structure is acyclical. * structure is acyclical.
* *
* @param indentFactor The number of spaces to add to each level of * @param indentFactor The number of spaces to add to each level of indentation.
* indentation.
* @param indent The indention of the top level. * @param indent The indention of the top level.
* @return a printable, displayable, transmittable * @return a printable, displayable, transmittable representation of the array.
* representation of the array.
* @throws JSONException * @throws JSONException
*/ */
String toString(int indentFactor, int indent) throws JSONException { String toString(int indentFactor, int indent) throws JSONException {
@@ -874,8 +771,7 @@ public class JSONArray {
int i; int i;
StringBuffer sb = new StringBuffer("["); StringBuffer sb = new StringBuffer("[");
if (len == 1) { if (len == 1) {
sb.append(JSONObject.valueToString(this.myArrayList.get(0), sb.append(JSONObject.valueToString(this.myArrayList.get(0), indentFactor, indent));
indentFactor, indent));
} else { } else {
int newindent = indent + indentFactor; int newindent = indent + indentFactor;
sb.append('\n'); sb.append('\n');
@@ -886,8 +782,7 @@ public class JSONArray {
for (int j = 0; j < newindent; j += 1) { for (int j = 0; j < newindent; j += 1) {
sb.append(' '); sb.append(' ');
} }
sb.append(JSONObject.valueToString(this.myArrayList.get(i), sb.append(JSONObject.valueToString(this.myArrayList.get(i), indentFactor, newindent));
indentFactor, newindent));
} }
sb.append('\n'); sb.append('\n');
for (i = 0; i < indent; i += 1) { for (i = 0; i < indent; i += 1) {
@@ -898,12 +793,11 @@ public class JSONArray {
return sb.toString(); return sb.toString();
} }
/** /**
* Write the contents of the JSONArray as JSON text to a writer. * Write the contents of the JSONArray as JSON text to a writer. For compactness, no whitespace is
* For compactness, no whitespace is added. * added.
* <p> *
* Warning: This method assumes that the data structure is acyclical. * <p>Warning: This method assumes that the data structure is acyclical.
* *
* @return The writer. * @return The writer.
* @throws JSONException * @throws JSONException
@@ -18,11 +18,9 @@ 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 * This provides static methods to convert an XML text into a JSONArray or JSONObject, and to covert
* JSONObject, and to covert a JSONArray or JSONObject into an XML text using * a JSONArray or JSONObject into an XML text using the JsonML transform.
* the JsonML transform.
* *
* @author JSON.org * @author JSON.org
* @version 2010-12-23 * @version 2010-12-23
@@ -34,13 +32,12 @@ public class JSONML {
* *
* @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 * @param ja The JSONArray that is containing the current tag or null if we are at the outermost
* if we are at the outermost level. * 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, private static Object parse(XMLTokener x, boolean arrayForm, JSONArray ja) throws JSONException {
JSONArray ja) throws JSONException {
String attribute; String attribute;
char c; char c;
String closeTag = null; String closeTag = null;
@@ -50,11 +47,11 @@ public class JSONML {
Object token; Object token;
String tagName = null; 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();
@@ -63,13 +60,11 @@ public class JSONML {
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( throw new JSONException("Expected a closing name instead of '" + token + "'.");
"Expected a closing name instead of '" +
token + "'.");
} }
if (x.nextToken() != XML.GT) { if (x.nextToken() != XML.GT) {
throw x.syntaxError("Misshaped close tag"); throw x.syntaxError("Misshaped close tag");
@@ -77,7 +72,7 @@ public class JSONML {
return token; return token;
} else if (token == XML.BANG) { } else if (token == XML.BANG) {
// <! // <!
c = x.next(); c = x.next();
if (c == '-') { if (c == '-') {
@@ -109,14 +104,14 @@ public class JSONML {
} }
} 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 { } else {
if (!(token instanceof String)) { if (!(token instanceof String)) {
@@ -148,7 +143,7 @@ public class JSONML {
break; break;
} }
// attribute = value // attribute = value
attribute = (String) token; attribute = (String) token;
if (!arrayForm && (attribute == "tagName" || attribute == "childNode")) { if (!arrayForm && (attribute == "tagName" || attribute == "childNode")) {
@@ -170,7 +165,7 @@ public class JSONML {
newja.put(newjo); newja.put(newjo);
} }
// Empty tag <.../> // Empty tag <.../>
if (token == XML.SLASH) { if (token == XML.SLASH) {
if (x.nextToken() != XML.GT) { if (x.nextToken() != XML.GT) {
@@ -184,7 +179,7 @@ public class JSONML {
} }
} }
// Content, between <...> and </...> // Content, between <...> and </...>
} else { } else {
if (token != XML.GT) { if (token != XML.GT) {
@@ -193,8 +188,7 @@ public class JSONML {
closeTag = (String) parse(x, arrayForm, newja); closeTag = (String) parse(x, arrayForm, newja);
if (closeTag != null) { if (closeTag != null) {
if (!closeTag.equals(tagName)) { if (!closeTag.equals(tagName)) {
throw x.syntaxError("Mismatched '" + tagName + throw x.syntaxError("Mismatched '" + tagName + "' and '" + closeTag + "'");
"' and '" + closeTag + "'");
} }
tagName = null; tagName = null;
if (!arrayForm && newja.length() > 0) { if (!arrayForm && newja.length() > 0) {
@@ -212,22 +206,18 @@ public class JSONML {
} }
} else { } else {
if (ja != null) { if (ja != null) {
ja.put(token instanceof String ? ja.put(token instanceof String ? XML.stringToValue((String) token) : token);
XML.stringToValue((String) token) : token);
} }
} }
} }
} }
/** /**
* Convert a well-formed (but not necessarily valid) XML string into a * Convert a well-formed (but not necessarily valid) XML string into a JSONArray using the JsonML
* JSONArray using the JsonML transform. Each XML tag is represented as * transform. Each XML tag is represented as a JSONArray in which the first element is the tag
* a JSONArray in which the first element is the tag name. If the tag has * name. If the tag has attributes, then the second element will be JSONObject containing the
* 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
* name/value pairs. If the tag contains children, then strings and * child tags. Comments, prologs, DTDs, and <code>&lt;[ [ ]]></code> are ignored.
* JSONArrays will represent the child tags.
* Comments, prologs, DTDs, and <code>&lt;[ [ ]]></code> are ignored.
* *
* @param string The source string. * @param string The source string.
* @return A JSONArray containing the structured data from the XML string. * @return A JSONArray containing the structured data from the XML string.
@@ -237,15 +227,12 @@ public class JSONML {
return toJSONArray(new XMLTokener(string)); return toJSONArray(new XMLTokener(string));
} }
/** /**
* Convert a well-formed (but not necessarily valid) XML string into a * Convert a well-formed (but not necessarily valid) XML string into a JSONArray using the JsonML
* JSONArray using the JsonML transform. Each XML tag is represented as * transform. Each XML tag is represented as a JSONArray in which the first element is the tag
* a JSONArray in which the first element is the tag name. If the tag has * name. If the tag has attributes, then the second element will be JSONObject containing the
* 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
* name/value pairs. If the tag contains children, then strings and * child content and tags. Comments, prologs, DTDs, and <code>&lt;[ [ ]]></code> are ignored.
* JSONArrays will represent the child content and tags.
* Comments, prologs, DTDs, and <code>&lt;[ [ ]]></code> are ignored.
* *
* @param x An XMLTokener. * @param x An XMLTokener.
* @return A JSONArray containing the structured data from the XML string. * @return A JSONArray containing the structured data from the XML string.
@@ -255,16 +242,14 @@ public class JSONML {
return (JSONArray) parse(x, true, null); return (JSONArray) parse(x, true, null);
} }
/** /**
* Convert a well-formed (but not necessarily valid) XML string into a * Convert a well-formed (but not necessarily valid) XML string into a JSONObject using the JsonML
* JSONObject using the JsonML transform. Each XML tag is represented as * transform. Each XML tag is represented as a JSONObject with a "tagName" property. If the tag
* a JSONObject with a "tagName" property. If the tag has attributes, then * has attributes, then the attributes will be in the JSONObject as properties. If the tag
* 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
* contains children, the object will have a "childNodes" property which * strings and JsonML JSONObjects.
* will be an array of strings and JsonML JSONObjects. *
* <p> * <p>Comments, prologs, DTDs, and <code>&lt;[ [ ]]></code> are ignored.
* Comments, prologs, DTDs, and <code>&lt;[ [ ]]></code> are ignored.
* *
* @param x An XMLTokener of the XML source text. * @param x An XMLTokener of the XML source text.
* @return A JSONObject containing the structured data from the XML string. * @return A JSONObject containing the structured data from the XML string.
@@ -274,16 +259,14 @@ public class JSONML {
return (JSONObject) parse(x, false, null); return (JSONObject) parse(x, false, null);
} }
/** /**
* Convert a well-formed (but not necessarily valid) XML string into a * Convert a well-formed (but not necessarily valid) XML string into a JSONObject using the JsonML
* JSONObject using the JsonML transform. Each XML tag is represented as * transform. Each XML tag is represented as a JSONObject with a "tagName" property. If the tag
* a JSONObject with a "tagName" property. If the tag has attributes, then * has attributes, then the attributes will be in the JSONObject as properties. If the tag
* 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
* contains children, the object will have a "childNodes" property which * strings and JsonML JSONObjects.
* will be an array of strings and JsonML JSONObjects. *
* <p> * <p>Comments, prologs, DTDs, and <code>&lt;[ [ ]]></code> are ignored.
* Comments, prologs, DTDs, and <code>&lt;[ [ ]]></code> are ignored.
* *
* @param string The XML source text. * @param string The XML source text.
* @return A JSONObject containing the structured data from the XML string. * @return A JSONObject containing the structured data from the XML string.
@@ -293,7 +276,6 @@ public class JSONML {
return toJSONObject(new XMLTokener(string)); return toJSONObject(new XMLTokener(string));
} }
/** /**
* Reverse the JSONML transformation, making an XML text from a JSONArray. * Reverse the JSONML transformation, making an XML text from a JSONArray.
* *
@@ -312,7 +294,7 @@ public class JSONML {
String tagName; String tagName;
String value; String value;
// Emit <tagName // Emit <tagName
tagName = ja.getString(0); tagName = ja.getString(0);
XML.noSpace(tagName); XML.noSpace(tagName);
@@ -325,7 +307,7 @@ public class JSONML {
i = 2; i = 2;
jo = (JSONObject) object; jo = (JSONObject) object;
// Emit the attributes // Emit the attributes
keys = jo.keys(); keys = jo.keys();
while (keys.hasNext()) { while (keys.hasNext()) {
@@ -345,7 +327,7 @@ public class JSONML {
i = 1; i = 1;
} }
//Emit content in body // Emit content in body
length = ja.length(); length = ja.length();
if (i >= length) { if (i >= length) {
@@ -375,10 +357,9 @@ public class JSONML {
} }
/** /**
* Reverse the JSONML transformation, making an XML text from a JSONObject. * Reverse the JSONML transformation, making an XML text from a JSONObject. The JSONObject must
* The JSONObject must contain a "tagName" property. If it has children, * contain a "tagName" property. If it has children, then it must have a "childNodes" property
* then it must have a "childNodes" property containing an array of objects. * containing an array of objects. The other properties are attributes with string values.
* The other properties are attributes with string values.
* *
* @param jo A JSONObject. * @param jo A JSONObject.
* @return An XML string. * @return An XML string.
@@ -395,7 +376,7 @@ public class JSONML {
String tagName; String tagName;
String value; String value;
//Emit <tagName // Emit <tagName
tagName = jo.optString("tagName"); tagName = jo.optString("tagName");
if (tagName == null) { if (tagName == null) {
@@ -406,7 +387,7 @@ public class JSONML {
sb.append('<'); sb.append('<');
sb.append(tagName); sb.append(tagName);
//Emit the attributes // Emit the attributes
keys = jo.keys(); keys = jo.keys();
while (keys.hasNext()) { while (keys.hasNext()) {
@@ -425,7 +406,7 @@ public class JSONML {
} }
} }
//Emit content in body // Emit content in body
ja = jo.optJSONArray("childNodes"); ja = jo.optJSONArray("childNodes");
if (ja == null) { if (ja == null) {
File diff suppressed because it is too large Load Diff
@@ -17,19 +17,17 @@
package dev.brighten.antivpn.utils.json; package dev.brighten.antivpn.utils.json;
/** /**
* The <code>JSONString</code> interface allows a <code>toJSONString()</code> * The <code>JSONString</code> interface allows a <code>toJSONString()</code> method so that a class
* method so that a class can change the behavior of * can change the behavior of <code>JSONObject.toString()</code>, <code>JSONArray.toString()</code>,
* <code>JSONObject.toString()</code>, <code>JSONArray.toString()</code>, * and <code>JSONWriter.value(</code>Object<code>)</code>. The <code>toJSONString</code> method will
* and <code>JSONWriter.value(</code>Object<code>)</code>. The * be used instead of the default behavior of using the Object's <code>toString()</code> method and
* <code>toJSONString</code> method will be used instead of the default behavior * quoting the result.
* 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 * The <code>toJSONString</code> method allows a class to produce its own JSON serialization.
* serialization.
* *
* @return A strictly syntactically correct JSON text. * @return A strictly syntactically correct JSON text.
*/ */
public String toJSONString(); String toJSONString();
} }
@@ -19,50 +19,49 @@ 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. * JSONStringer provides a quick and convenient way of producing JSON text. The texts produced
* The texts produced strictly conform to JSON syntax rules. No whitespace is * strictly conform to JSON syntax rules. No whitespace is added, so the results are ready for
* added, so the results are ready for transmission or storage. Each instance of * transmission or storage. Each instance of JSONStringer can produce one JSON text.
* JSONStringer can produce one JSON text. *
* <p> * <p>A JSONStringer instance provides a <code>value</code> method for appending values to the text,
* A JSONStringer instance provides a <code>value</code> method for appending * and a <code>key</code> method for adding keys before values in objects. There are <code>array
* values to the * </code> and <code>endArray</code> methods that make and bound array values, and <code>object
* text, and a <code>key</code> * </code> and <code>endObject</code> methods which make and bound object values. All of these
* method for adding keys before values in objects. There are <code>array</code> * methods return the JSONWriter instance, permitting cascade style. For example,
* and <code>endArray</code> methods that make and bound array values, and *
* <code>object</code> and <code>endObject</code> methods which make and bound * <pre>
* 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> which produces the string <pre> * .toString();</pre>
*
* which produces the string
*
* <pre>
* {"JSON":"Hello, World!"}</pre> * {"JSON":"Hello, World!"}</pre>
* <p> *
* The first method called must be <code>array</code> or <code>object</code>. * <p>The first method called must be <code>array</code> or <code>object</code>. There are no
* There are no methods for adding commas or colons. JSONStringer adds them for * methods for adding commas or colons. JSONStringer adds them for you. Objects and arrays can be
* you. Objects and arrays can be nested up to 20 levels deep. * nested up to 20 levels deep.
* <p> *
* This can sometimes be easier than using a JSONObject to build a string. * <p>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. */
* Make a fresh JSONStringer. It can be used to build one JSON text.
*/
public JSONStringer() { public JSONStringer() {
super(new StringWriter()); super(new StringWriter());
} }
/** /**
* Return the JSON text. This method is used to obtain the product of the * Return the JSON text. This method is used to obtain the product of the JSONStringer instance.
* JSONStringer instance. It will return <code>null</code> if there was a * It will return <code>null</code> if there was a problem in the construction of the JSON text
* problem in the construction of the JSON text (such as the calls to * (such as the calls to <code>array</code> were not properly balanced with calls to <code>
* <code>array</code> were not properly balanced with calls to * endArray</code>).
* <code>endArray</code>).
* *
* @return The JSON text. * @return The JSON text.
*/ */
@@ -19,9 +19,8 @@ package dev.brighten.antivpn.utils.json;
import java.io.*; import java.io.*;
/** /**
* A JSONTokener takes a source string and extracts characters and tokens from * A JSONTokener takes a source string and extracts characters and tokens from it. It is used by the
* it. It is used by the JSONObject and JSONArray constructors to parse * JSONObject and JSONArray constructors to parse JSON source strings.
* JSON source strings.
* *
* @author JSON.org * @author JSON.org
* @version 2010-12-24 * @version 2010-12-24
@@ -36,15 +35,13 @@ public class JSONTokener {
private Reader reader; private Reader reader;
private boolean usePrevious; private boolean usePrevious;
/** /**
* Construct a JSONTokener from a Reader. * Construct a JSONTokener from a Reader.
* *
* @param reader A reader. * @param reader A reader.
*/ */
public JSONTokener(Reader reader) { public JSONTokener(Reader reader) {
this.reader = reader.markSupported() ? this.reader = reader.markSupported() ? reader : new BufferedReader(reader);
reader : new BufferedReader(reader);
this.eof = false; this.eof = false;
this.usePrevious = false; this.usePrevious = false;
this.previous = 0; this.previous = 0;
@@ -53,15 +50,11 @@ public class JSONTokener {
this.line = 1; this.line = 1;
} }
/** Construct a JSONTokener from an InputStream. */
/**
* Construct a JSONTokener from an InputStream.
*/
public JSONTokener(InputStream inputStream) throws JSONException { public JSONTokener(InputStream inputStream) throws JSONException {
this(new InputStreamReader(inputStream)); this(new InputStreamReader(inputStream));
} }
/** /**
* Construct a JSONTokener from a string. * Construct a JSONTokener from a string.
* *
@@ -74,8 +67,7 @@ public class JSONTokener {
/** /**
* Get the hex value of a character (base16). * Get the hex value of a character (base16).
* *
* @param c A character between '0' and '9' or between 'A' and 'F' or * @param c A character between '0' and '9' or between 'A' and 'F' or between 'a' and 'f'.
* between 'a' and 'f'.
* @return An int between 0 and 15, or -1 if c was not a hex digit. * @return An int between 0 and 15, or -1 if c was not a hex digit.
*/ */
public static int dehexchar(char c) { public static int dehexchar(char c) {
@@ -92,9 +84,8 @@ public class JSONTokener {
} }
/** /**
* Back up one character. This provides a sort of lookahead capability, * Back up one character. This provides a sort of lookahead capability, so that you can test for a
* so that you can test for a digit or letter before attempting to parse * digit or letter before attempting to parse the next number or identifier.
* the next number or identifier.
*/ */
public void back() throws JSONException { public void back() throws JSONException {
if (usePrevious || index <= 0) { if (usePrevious || index <= 0) {
@@ -110,10 +101,8 @@ public class JSONTokener {
return eof && !usePrevious; return eof && !usePrevious;
} }
/** /**
* Determine if the source string still contains characters that next() * Determine if the source string still contains characters that next() can consume.
* can consume.
* *
* @return true if not yet at the end of the source. * @return true if not yet at the end of the source.
*/ */
@@ -126,7 +115,6 @@ public class JSONTokener {
return true; return true;
} }
/** /**
* Get the next character in the source string. * Get the next character in the source string.
* *
@@ -163,10 +151,8 @@ public class JSONTokener {
return this.previous; return this.previous;
} }
/** /**
* Consume the next character, and check that it matches a specified * Consume the next character, and check that it matches a specified character.
* character.
* *
* @param c The character to match. * @param c The character to match.
* @return The character. * @return The character.
@@ -175,20 +161,18 @@ public class JSONTokener {
public char next(char c) throws JSONException { public char next(char c) throws JSONException {
char n = next(); char n = next();
if (n != c) { if (n != c) {
throw syntaxError("Expected '" + c + "' and instead saw '" + throw syntaxError("Expected '" + c + "' and instead saw '" + n + "'");
n + "'");
} }
return n; return n;
} }
/** /**
* Get the next n characters. * Get the next n characters.
* *
* @param n The number of characters to take. * @param n The number of characters to take.
* @return A string of n characters. * @return A string of n characters.
* @throws JSONException Substring bounds error if there are not * @throws JSONException Substring bounds error if there are not n characters remaining in the
* n characters remaining in the source string. * source string.
*/ */
public String next(int n) throws JSONException { public String next(int n) throws JSONException {
if (n == 0) { if (n == 0) {
@@ -208,7 +192,6 @@ public class JSONTokener {
return new String(chars); return new String(chars);
} }
/** /**
* Get the next char in the string, skipping whitespace. * Get the next char in the string, skipping whitespace.
* *
@@ -224,15 +207,12 @@ public class JSONTokener {
} }
} }
/** /**
* Return the characters up to the next close quote character. * Return the characters up to the next close quote character. Backslash processing is done. The
* Backslash processing is done. The formal JSON format does not * formal JSON format does not allow strings in single quotes, but an implementation is allowed to
* allow strings in single quotes, but an implementation is allowed to
* accept them. * accept them.
* *
* @param quote The quoting character, either * @param quote The quoting character, either <code>"</code>&nbsp;<small>(double quote)</small> or
* <code>"</code>&nbsp;<small>(double quote)</small> or
* <code>'</code>&nbsp;<small>(single quote)</small>. * <code>'</code>&nbsp;<small>(single quote)</small>.
* @return A String. * @return A String.
* @throws JSONException Unterminated string. * @throws JSONException Unterminated string.
@@ -287,10 +267,9 @@ public class JSONTokener {
} }
} }
/** /**
* Get the text up but not including the specified character or the * Get the text up but not including the specified character or the end of line, whichever comes
* end of line, whichever comes first. * first.
* *
* @param delimiter A delimiter character. * @param delimiter A delimiter character.
* @return A string. * @return A string.
@@ -309,10 +288,9 @@ public class JSONTokener {
} }
} }
/** /**
* Get the text up but not including one of the specified delimiter * Get the text up but not including one of the specified delimiter characters or the end of line,
* characters or the end of line, whichever comes first. * whichever comes first.
* *
* @param delimiters A set of delimiter characters. * @param delimiters A set of delimiter characters.
* @return A string, trimmed. * @return A string, trimmed.
@@ -322,8 +300,7 @@ public class JSONTokener {
StringBuffer sb = new StringBuffer(); StringBuffer sb = new StringBuffer();
for (; ; ) { for (; ; ) {
c = next(); c = next();
if (delimiters.indexOf(c) >= 0 || c == 0 || if (delimiters.indexOf(c) >= 0 || c == 0 || c == '\n' || c == '\r') {
c == '\n' || c == '\r') {
if (c != 0) { if (c != 0) {
back(); back();
} }
@@ -333,10 +310,9 @@ public class JSONTokener {
} }
} }
/** /**
* Get the next value. The value can be a Boolean, Double, Integer, * Get the next value. The value can be a Boolean, Double, Integer, JSONArray, JSONObject, Long,
* JSONArray, JSONObject, Long, or String, or the JSONObject.NULL object. * or String, or the JSONObject.NULL object.
* *
* @return An object. * @return An object.
* @throws JSONException If syntax error. * @throws JSONException If syntax error.
@@ -374,20 +350,18 @@ public class JSONTokener {
back(); back();
string = sb.toString().trim(); string = sb.toString().trim();
if (string.equals("")) { if (string.isEmpty()) {
throw syntaxError("Missing value"); throw syntaxError("Missing value");
} }
return JSONObject.stringToValue(string); return JSONObject.stringToValue(string);
} }
/** /**
* Skip characters until the next character is the requested character. * Skip characters until the next character is the requested character. If the requested character
* If the requested character is not found, no characters are skipped. * is not found, no characters are skipped.
* *
* @param to A character to skip to. * @param to A character to skip to.
* @return The requested character, or zero if the requested character * @return The requested character, or zero if the requested character is not found.
* is not found.
*/ */
public char skipTo(char to) throws JSONException { public char skipTo(char to) throws JSONException {
char c; char c;
@@ -414,7 +388,6 @@ public class JSONTokener {
return c; return c;
} }
/** /**
* Make a JSONException to signal a syntax error. * Make a JSONException to signal a syntax error.
* *
@@ -425,14 +398,12 @@ public class JSONTokener {
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() { public String toString() {
return " at " + index + " [character " + this.character + " line " + return " at " + index + " [character " + this.character + " line " + this.line + "]";
this.line + "]";
} }
} }
@@ -20,67 +20,56 @@ import java.io.IOException;
import java.io.Writer; import java.io.Writer;
/** /**
* JSONWriter provides a quick and convenient way of producing JSON text. * JSONWriter provides a quick and convenient way of producing JSON text. The texts produced
* The texts produced strictly conform to JSON syntax rules. No whitespace is * strictly conform to JSON syntax rules. No whitespace is added, so the results are ready for
* added, so the results are ready for transmission or storage. Each instance of * transmission or storage. Each instance of JSONWriter can produce one JSON text.
* JSONWriter can produce one JSON text. *
* <p> * <p>A JSONWriter instance provides a <code>value</code> method for appending values to the text,
* A JSONWriter instance provides a <code>value</code> method for appending * and a <code>key</code> method for adding keys before values in objects. There are <code>array
* values to the * </code> and <code>endArray</code> methods that make and bound array values, and <code>object
* text, and a <code>key</code> * </code> and <code>endObject</code> methods which make and bound object values. All of these
* method for adding keys before values in objects. There are <code>array</code> * methods return the JSONWriter instance, permitting a cascade style. For example,
* and <code>endArray</code> methods that make and bound array values, and *
* <code>object</code> and <code>endObject</code> methods which make and bound * <pre>
* 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> which writes <pre> * .endObject();</pre>
*
* which writes
*
* <pre>
* {"JSON":"Hello, World!"}</pre> * {"JSON":"Hello, World!"}</pre>
* <p> *
* The first method called must be <code>array</code> or <code>object</code>. * <p>The first method called must be <code>array</code> or <code>object</code>. There are no
* There are no methods for adding commas or colons. JSONWriter adds them for * methods for adding commas or colons. JSONWriter adds them for you. Objects and arrays can be
* you. Objects and arrays can be nested up to 20 levels deep. * nested up to 20 levels deep.
* <p> *
* This can sometimes be easier than using a JSONObject to build a string. * <p>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: /** The current mode. Values: 'a' (array), 'd' (done), 'i' (initial), 'k' (key), 'o' (object). */
* 'a' (array),
* 'd' (done),
* 'i' (initial),
* 'k' (key),
* 'o' (object).
*/
protected char mode; protected char mode;
/**
* The writer that will receive the output. /** The writer that will receive the output. */
*/
protected Writer writer; protected Writer writer;
/**
* The comma flag determines if a comma should be output before the next /** The comma flag determines if a comma should be output before the next value. */
* value.
*/
private boolean comma; private boolean comma;
/**
* The object/array stack. /** The object/array stack. */
*/
private JSONObject stack[]; private JSONObject stack[];
/**
* The stack top index. A value of 0 indicates that the stack is empty. /** The stack top index. A value of 0 indicates that the stack is empty. */
*/
private int top; private int top;
/** /** Make a fresh JSONWriter. It can be used to build one JSON text. */
* Make a fresh JSONWriter. It can be used to build one JSON text.
*/
public JSONWriter(Writer w) { public JSONWriter(Writer w) {
this.comma = false; this.comma = false;
this.mode = 'i'; this.mode = 'i';
@@ -119,14 +108,13 @@ public class JSONWriter {
} }
/** /**
* Begin appending a new array. All values until the balancing * Begin appending a new array. All values until the balancing <code>endArray</code> will be
* <code>endArray</code> will be appended to this array. The * appended to this array. The <code>endArray</code> method must be called to mark the array's
* <code>endArray</code> method must be called to mark the array's end. * end.
* *
* @return this * @return this
* @throws JSONException If the nesting is too deep, or if the object is * @throws JSONException If the nesting is too deep, or if the object is started in the wrong
* started in the wrong place (for example as a key or after the end of the * place (for example as a key or after the end of the outermost array or object).
* outermost array or object).
*/ */
public JSONWriter array() throws JSONException { public JSONWriter array() throws JSONException {
if (this.mode == 'i' || this.mode == 'o' || this.mode == 'a') { if (this.mode == 'i' || this.mode == 'o' || this.mode == 'a') {
@@ -148,8 +136,7 @@ public class JSONWriter {
*/ */
private JSONWriter end(char mode, char c) throws JSONException { private JSONWriter end(char mode, char c) throws JSONException {
if (this.mode != mode) { if (this.mode != mode) {
throw new JSONException(mode == 'a' ? "Misplaced endArray." : throw new JSONException(mode == 'a' ? "Misplaced endArray." : "Misplaced endObject.");
"Misplaced endObject.");
} }
this.pop(mode); this.pop(mode);
try { try {
@@ -162,8 +149,7 @@ public class JSONWriter {
} }
/** /**
* End an array. This method most be called to balance calls to * End an array. This method most be called to balance calls to <code>array</code>.
* <code>array</code>.
* *
* @return this * @return this
* @throws JSONException If incorrectly nested. * @throws JSONException If incorrectly nested.
@@ -173,8 +159,7 @@ public class JSONWriter {
} }
/** /**
* End an object. This method most be called to balance calls to * End an object. This method most be called to balance calls to <code>object</code>.
* <code>object</code>.
* *
* @return this * @return this
* @throws JSONException If incorrectly nested. * @throws JSONException If incorrectly nested.
@@ -184,13 +169,13 @@ public class JSONWriter {
} }
/** /**
* Append a key. The key will be associated with the next value. In an * Append a key. The key will be associated with the next value. In an object, every value must be
* object, every value must be preceded by a key. * preceded by a key.
* *
* @param string A key string. * @param string A key string.
* @return this * @return this
* @throws JSONException If the key is out of place. For example, keys * @throws JSONException If the key is out of place. For example, keys do not belong in arrays or
* do not belong in arrays or if the key is null. * if the key is null.
*/ */
public JSONWriter key(String string) throws JSONException { public JSONWriter key(String string) throws JSONException {
if (string == null) { if (string == null) {
@@ -214,16 +199,14 @@ public class JSONWriter {
throw new JSONException("Misplaced key."); throw new JSONException("Misplaced key.");
} }
/** /**
* Begin appending a new object. All keys and values until the balancing * Begin appending a new object. All keys and values until the balancing <code>endObject</code>
* <code>endObject</code> will be appended to this object. The * will be appended to this object. The <code>endObject</code> method must be called to mark the
* <code>endObject</code> method must be called to mark the object's end. * object's end.
* *
* @return this * @return this
* @throws JSONException If the nesting is too deep, or if the object is * @throws JSONException If the nesting is too deep, or if the object is started in the wrong
* started in the wrong place (for example as a key or after the end of the * place (for example as a key or after the end of the outermost array or object).
* outermost array or object).
*/ */
public JSONWriter object() throws JSONException { public JSONWriter object() throws JSONException {
if (this.mode == 'i') { if (this.mode == 'i') {
@@ -236,10 +219,8 @@ public class JSONWriter {
return this; return this;
} }
throw new JSONException("Misplaced object."); throw new JSONException("Misplaced object.");
} }
/** /**
* Pop an array or object scope. * Pop an array or object scope.
* *
@@ -255,8 +236,7 @@ public class JSONWriter {
throw new JSONException("Nesting error."); throw new JSONException("Nesting error.");
} }
this.top -= 1; this.top -= 1;
this.mode = this.top == 0 ? this.mode = this.top == 0 ? 'd' : this.stack[this.top - 1] == null ? 'a' : 'k';
'd' : this.stack[this.top - 1] == null ? 'a' : 'k';
} }
/** /**
@@ -274,10 +254,8 @@ public class JSONWriter {
this.top += 1; this.top += 1;
} }
/** /**
* Append either the value <code>true</code> or the value * Append either the value <code>true</code> or the value <code>false</code>.
* <code>false</code>.
* *
* @param b A boolean. * @param b A boolean.
* @return this * @return this
@@ -309,12 +287,11 @@ public class JSONWriter {
return this.append(Long.toString(l)); return this.append(Long.toString(l));
} }
/** /**
* Append an object value. * Append an object value.
* *
* @param object The object to append. It can be null, or a Boolean, Number, * @param object The object to append. It can be null, or a Boolean, Number, String, JSONObject,
* String, JSONObject, or JSONArray, or an object that implements JSONString. * or JSONArray, or an object that implements JSONString.
* @return this * @return this
* @throws JSONException If the value is out of sequence. * @throws JSONException If the value is out of sequence.
*/ */
@@ -15,6 +15,7 @@
*/ */
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;
@@ -18,63 +18,45 @@ 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 JSONObject, * This provides static methods to convert an XML text into a JSONObject, and to covert a JSONObject
* and to covert a JSONObject into an XML text. * into an XML text.
* *
* @author JSON.org * @author JSON.org
* @version 2011-02-11 * @version 2011-02-11
*/ */
public class XML { public class XML {
/** /** The Character '&'. */
* The Character '&'.
*/
public static final Character AMP = new Character('&'); public static final Character AMP = new Character('&');
/** /** The Character '''. */
* The Character '''.
*/
public static final Character APOS = new Character('\''); public static final Character APOS = new Character('\'');
/** /** The Character '!'. */
* The Character '!'.
*/
public static final Character BANG = new Character('!'); public static final Character BANG = new Character('!');
/** /** The Character '='. */
* The Character '='.
*/
public static final Character EQ = new Character('='); public static final Character EQ = new Character('=');
/** /** The Character '>'. */
* The Character '>'.
*/
public static final Character GT = new Character('>'); public static final Character GT = new Character('>');
/** /** The Character '<'. */
* The Character '<'.
*/
public static final Character LT = new Character('<'); public static final Character LT = new Character('<');
/** /** The Character '?'. */
* The Character '?'.
*/
public static final Character QUEST = new Character('?'); public static final Character QUEST = new Character('?');
/** /** The Character '"'. */
* The Character '"'.
*/
public static final Character QUOT = new Character('"'); public static final Character QUOT = new Character('"');
/** /** The Character '/'. */
* The Character '/'.
*/
public static final Character SLASH = new Character('/'); public static final Character SLASH = new Character('/');
/** /**
* Replace special characters with XML escapes: * Replace special characters with XML escapes:
*
* <pre> * <pre>
* &amp; <small>(ampersand)</small> is replaced by &amp;amp; * &amp; <small>(ampersand)</small> is replaced by &amp;amp;
* &lt; <small>(less than)</small> is replaced by &amp;lt; * &lt; <small>(less than)</small> is replaced by &amp;lt;
@@ -113,8 +95,8 @@ public class XML {
} }
/** /**
* Throw an exception if the string contains whitespace. * Throw an exception if the string contains whitespace. Whitespace is not allowed in tagNames and
* Whitespace is not allowed in tagNames and attributes. * attributes.
* *
* @param string * @param string
* @throws JSONException * @throws JSONException
@@ -126,8 +108,7 @@ public class XML {
} }
for (i = 0; i < length; i += 1) { for (i = 0; i < length; i += 1) {
if (Character.isWhitespace(string.charAt(i))) { if (Character.isWhitespace(string.charAt(i))) {
throw new JSONException("'" + string + throw new JSONException("'" + string + "' contains a space character.");
"' contains a space character.");
} }
} }
} }
@@ -141,8 +122,7 @@ public class XML {
* @return true if the close tag is processed. * @return true if the close tag is processed.
* @throws JSONException * @throws JSONException
*/ */
private static boolean parse(XMLTokener x, JSONObject context, private static boolean parse(XMLTokener x, JSONObject context, String name) throws JSONException {
String name) throws JSONException {
char c; char c;
int i; int i;
JSONObject jsonobject = null; JSONObject jsonobject = null;
@@ -150,19 +130,19 @@ public class XML {
String tagName; String tagName;
Object token; Object token;
// Test for and skip past these forms: // Test for and skip past these forms:
// <!-- ... --> // <!-- ... -->
// <! ... > // <! ... >
// <![ ... ]]> // <![ ... ]]>
// <? ... ?> // <? ... ?>
// Report errors for these forms: // Report errors for these forms:
// <> // <>
// <= // <=
// << // <<
token = x.nextToken(); token = x.nextToken();
// <! // <!
if (token == BANG) { if (token == BANG) {
c = x.next(); c = x.next();
@@ -199,13 +179,13 @@ public class XML {
return false; return false;
} else if (token == QUEST) { } else if (token == QUEST) {
// <? // <?
x.skipPast("?>"); x.skipPast("?>");
return false; return false;
} else if (token == SLASH) { } else if (token == SLASH) {
// Close tag </ // Close tag </
token = x.nextToken(); token = x.nextToken();
if (name == null) { if (name == null) {
@@ -222,7 +202,7 @@ public class XML {
} else if (token instanceof Character) { } else if (token instanceof Character) {
throw x.syntaxError("Misshaped tag"); throw x.syntaxError("Misshaped tag");
// Open tag < // Open tag <
} else { } else {
tagName = (String) token; tagName = (String) token;
@@ -233,7 +213,7 @@ public class XML {
token = x.nextToken(); token = x.nextToken();
} }
// attribute = value // attribute = value
if (token instanceof String) { if (token instanceof String) {
string = (String) token; string = (String) token;
@@ -243,14 +223,13 @@ public class XML {
if (!(token instanceof String)) { if (!(token instanceof String)) {
throw x.syntaxError("Missing value"); throw x.syntaxError("Missing value");
} }
jsonobject.accumulate(string, jsonobject.accumulate(string, XML.stringToValue((String) token));
XML.stringToValue((String) token));
token = null; token = null;
} else { } else {
jsonobject.accumulate(string, ""); jsonobject.accumulate(string, "");
} }
// Empty tag <.../> // Empty tag <.../>
} else if (token == SLASH) { } else if (token == SLASH) {
if (x.nextToken() != GT) { if (x.nextToken() != GT) {
@@ -263,7 +242,7 @@ public class XML {
} }
return false; return false;
// Content, between <...> and </...> // Content, between <...> and </...>
} else if (token == GT) { } else if (token == GT) {
for (; ; ) { for (; ; ) {
@@ -276,20 +255,17 @@ public class XML {
} else if (token instanceof String) { } else if (token instanceof String) {
string = (String) token; string = (String) token;
if (string.length() > 0) { if (string.length() > 0) {
jsonobject.accumulate("content", jsonobject.accumulate("content", XML.stringToValue(string));
XML.stringToValue(string));
} }
// Nested element // Nested element
} else if (token == LT) { } else if (token == LT) {
if (parse(x, jsonobject, tagName)) { if (parse(x, jsonobject, tagName)) {
if (jsonobject.length() == 0) { if (jsonobject.length() == 0) {
context.accumulate(tagName, ""); context.accumulate(tagName, "");
} else if (jsonobject.length() == 1 && } else if (jsonobject.length() == 1 && jsonobject.opt("content") != null) {
jsonobject.opt("content") != null) { context.accumulate(tagName, jsonobject.opt("content"));
context.accumulate(tagName,
jsonobject.opt("content"));
} else { } else {
context.accumulate(tagName, jsonobject); context.accumulate(tagName, jsonobject);
} }
@@ -304,19 +280,17 @@ public class XML {
} }
} }
/** /**
* Try to convert a string into a number, boolean, or null. If the string * Try to convert a string into a number, boolean, or null. If the string can't be converted,
* can't be converted, return the string. This is much less ambitious than * return the string. This is much less ambitious than JSONObject.stringToValue, especially
* JSONObject.stringToValue, especially because it does not attempt to * because it does not attempt to convert plus forms, octal forms, hex forms, or E forms lacking
* convert plus forms, octal forms, hex forms, or E forms lacking decimal * decimal points.
* points.
* *
* @param string A String. * @param string A String.
* @return A simple JSON value. * @return A simple JSON value.
*/ */
public static Object stringToValue(String string) { public static Object stringToValue(String string) {
if (string.equals("")) { if (string.isEmpty()) {
return string; return string;
} }
if (string.equalsIgnoreCase("true")) { if (string.equalsIgnoreCase("true")) {
@@ -332,8 +306,8 @@ public class XML {
return new Integer(0); return new Integer(0);
} }
// If it might be a number, try converting it. If that doesn't work, // If it might be a number, try converting it. If that doesn't work,
// return the string. // return the string.
try { try {
char initial = string.charAt(0); char initial = string.charAt(0);
@@ -362,16 +336,13 @@ public class XML {
return string; return string;
} }
/** /**
* Convert a well-formed (but not necessarily valid) XML string into a * Convert a well-formed (but not necessarily valid) XML string into a JSONObject. Some
* JSONObject. Some information may be lost in this transformation * information may be lost in this transformation because JSON is a data format and XML is a
* because JSON is a data format and XML is a document format. XML uses * document format. XML uses elements, attributes, and content text, while JSON uses unordered
* elements, attributes, and content text, while JSON uses unordered * collections of name/value pairs and arrays of values. JSON does not does not like to
* collections of name/value pairs and arrays of values. JSON does not * distinguish between elements and attributes. Sequences of similar elements are represented as
* does not like to distinguish between elements and attributes. * JSONArrays. Content text may be placed in a "content" member. Comments, prologs, DTDs, and
* Sequences of similar elements are represented as JSONArrays. Content
* text may be placed in a "content" member. Comments, prologs, DTDs, and
* <code>&lt;[ [ ]]></code> are ignored. * <code>&lt;[ [ ]]></code> are ignored.
* *
* @param string The source string. * @param string The source string.
@@ -387,7 +358,6 @@ public class XML {
return jo; return jo;
} }
/** /**
* Convert a JSONObject into a well-formed, element-normal XML string. * Convert a JSONObject into a well-formed, element-normal XML string.
* *
@@ -399,7 +369,6 @@ public class XML {
return toString(object, null); return toString(object, null);
} }
/** /**
* Convert a JSONObject into a well-formed, element-normal XML string. * Convert a JSONObject into a well-formed, element-normal XML string.
* *
@@ -408,8 +377,7 @@ public class XML {
* @return A string. * @return A string.
* @throws JSONException * @throws JSONException
*/ */
public static String toString(Object object, String tagName) public static String toString(Object object, String tagName) throws JSONException {
throws JSONException {
StringBuffer sb = new StringBuffer(); StringBuffer sb = new StringBuffer();
int i; int i;
JSONArray ja; JSONArray ja;
@@ -421,7 +389,7 @@ public class XML {
Object value; Object value;
if (object instanceof JSONObject) { if (object instanceof JSONObject) {
// Emit <tagName> // Emit <tagName>
if (tagName != null) { if (tagName != null) {
sb.append('<'); sb.append('<');
@@ -429,7 +397,7 @@ public class XML {
sb.append('>'); sb.append('>');
} }
// Loop thru the keys. // Loop thru the keys.
jo = (JSONObject) object; jo = (JSONObject) object;
keys = jo.keys(); keys = jo.keys();
@@ -445,7 +413,7 @@ public class XML {
string = null; string = null;
} }
// Emit content in body // Emit content in body
if (key.equals("content")) { if (key.equals("content")) {
if (value instanceof JSONArray) { if (value instanceof JSONArray) {
@@ -461,7 +429,7 @@ public class XML {
sb.append(escape(value.toString())); sb.append(escape(value.toString()));
} }
// Emit an array of similar keys // Emit an array of similar keys
} else if (value instanceof JSONArray) { } else if (value instanceof JSONArray) {
ja = (JSONArray) value; ja = (JSONArray) value;
@@ -485,7 +453,7 @@ public class XML {
sb.append(key); sb.append(key);
sb.append("/>"); sb.append("/>");
// Emit a new tag <k> // Emit a new tag <k>
} else { } else {
sb.append(toString(value, key)); sb.append(toString(value, key));
@@ -493,7 +461,7 @@ public class XML {
} }
if (tagName != null) { if (tagName != null) {
// Emit the </tagname> close tag // Emit the </tagname> close tag
sb.append("</"); sb.append("</");
sb.append(tagName); sb.append(tagName);
@@ -501,8 +469,8 @@ public class XML {
} }
return sb.toString(); return sb.toString();
// XML does not have good support for arrays. If an array appears in a place // XML does not have good support for arrays. If an array appears in a place
// where XML is lacking, synthesize an <array> element. // where XML is lacking, synthesize an <array> element.
} else { } else {
if (object.getClass().isArray()) { if (object.getClass().isArray()) {
@@ -517,9 +485,11 @@ public class XML {
return sb.toString(); return sb.toString();
} else { } else {
string = (object == null) ? "null" : escape(object.toString()); string = (object == null) ? "null" : escape(object.toString());
return (tagName == null) ? "\"" + string + "\"" : return (tagName == null)
(string.length() == 0) ? "<" + tagName + "/>" : ? "\"" + string + "\""
"<" + tagName + ">" + string + "</" + tagName + ">"; : (string.isEmpty())
? "<" + tagName + "/>"
: "<" + tagName + ">" + string + "</" + tagName + ">";
} }
} }
} }
@@ -17,18 +17,16 @@
package dev.brighten.antivpn.utils.json; package dev.brighten.antivpn.utils.json;
/** /**
* The XMLTokener extends the JSONTokener to provide additional methods * The XMLTokener extends the JSONTokener to provide additional methods for the parsing of XML
* for the parsing of XML texts. * texts.
* *
* @author JSON.org * @author JSON.org
* @version 2010-12-24 * @version 2010-12-24
*/ */
public class XMLTokener extends JSONTokener { public class XMLTokener extends JSONTokener {
/** /**
* The table of entity values. It initially contains Character values for * The table of entity values. It initially contains Character values for amp, apos, gt, lt, quot.
* amp, apos, gt, lt, quot.
*/ */
public static final java.util.HashMap entity; public static final java.util.HashMap entity;
@@ -67,22 +65,18 @@ public class XMLTokener extends JSONTokener {
} }
sb.append(c); sb.append(c);
i = sb.length() - 3; i = sb.length() - 3;
if (i >= 0 && sb.charAt(i) == ']' && if (i >= 0 && sb.charAt(i) == ']' && sb.charAt(i + 1) == ']' && sb.charAt(i + 2) == '>') {
sb.charAt(i + 1) == ']' && sb.charAt(i + 2) == '>') {
sb.setLength(i); sb.setLength(i);
return sb.toString(); return sb.toString();
} }
} }
} }
/** /**
* Get the next XML outer token, trimming whitespace. There are two kinds * Get the next XML outer token, trimming whitespace. There are two kinds of tokens: the '<'
* of tokens: the '<' character which begins a markup tag, and the content * character which begins a markup tag, and the content text between markup tags.
* text between markup tags.
* *
* @return A string, or a '<' Character, or null if there is no more * @return A string, or a '<' Character, or null if there is no more source text.
* source text.
* @throws JSONException * @throws JSONException
*/ */
public Object nextContent() throws JSONException { public Object nextContent() throws JSONException {
@@ -112,10 +106,9 @@ public class XMLTokener extends JSONTokener {
} }
} }
/** /**
* Return the next entity. These entities are translated to Characters: * Return the next entity. These entities are translated to Characters: <code>
* <code>&amp; &apos; &gt; &lt; &quot;</code>. * &amp; &apos; &gt; &lt; &quot;</code>.
* *
* @param ampersand An ampersand character. * @param ampersand An ampersand character.
* @return A Character or an entity String if the entity is not recognized. * @return A Character or an entity String if the entity is not recognized.
@@ -138,16 +131,12 @@ public class XMLTokener extends JSONTokener {
return object != null ? object : ampersand + string + ";"; return object != null ? object : ampersand + string + ";";
} }
/** /**
* Returns the next XML meta token. This is used for skipping over <!...> * Returns the next XML meta token. This is used for skipping over <!...> and <?...?> structures.
* and <?...?> structures.
* *
* @return Syntax characters (<code>< > / = ! ?</code>) are returned as * @return Syntax characters (<code>< > / = ! ?</code>) are returned as Character, and strings and
* Character, and strings and names are returned as Boolean. We don't care * names are returned as Boolean. We don't care what the values actually are.
* what the values actually are. * @throws JSONException If a string is not properly closed or if the XML is badly structured.
* @throws JSONException If a string is not properly closed or if the XML
* is badly structured.
*/ */
public Object nextMeta() throws JSONException { public Object nextMeta() throws JSONException {
char c; char c;
@@ -205,12 +194,10 @@ public class XMLTokener extends JSONTokener {
} }
} }
/** /**
* Get the next XML Token. These tokens are found inside of angle * Get the next XML Token. These tokens are found inside of angle brackets. It may be one of these
* brackets. It may be one of these characters: <code>/ > = ! ?</code> or it * characters: <code>/ > = ! ?</code> or it may be a string wrapped in single quotes or double
* may be a string wrapped in single quotes or double quotes, or it may be a * quotes, or it may be a name.
* name.
* *
* @return a String or a Character. * @return a String or a Character.
* @throws JSONException If the XML is not well formed. * @throws JSONException If the XML is not well formed.
@@ -238,7 +225,7 @@ public class XMLTokener extends JSONTokener {
case '?': case '?':
return XML.QUEST; return XML.QUEST;
// Quoted string // Quoted string
case '"': case '"':
case '\'': case '\'':
@@ -260,7 +247,7 @@ public class XMLTokener extends JSONTokener {
} }
default: default:
// Name // Name
sb = new StringBuffer(); sb = new StringBuffer();
for (; ; ) { for (; ; ) {
@@ -290,10 +277,9 @@ public class XMLTokener extends JSONTokener {
} }
} }
/** /**
* Skip characters until past the requested string. * Skip characters until past the requested string. If it is not found, we are left at the end of
* If it is not found, we are left at the end of the source with a result of false. * the source with a result of false.
* *
* @param to A string to skip past. * @param to A string to skip past.
* @throws JSONException * @throws JSONException
@@ -21,13 +21,11 @@ import dev.brighten.antivpn.utils.json.JSONObject;
import dev.brighten.antivpn.utils.json.JsonReader; import dev.brighten.antivpn.utils.json.JsonReader;
import dev.brighten.antivpn.web.objects.QueryResponse; import dev.brighten.antivpn.web.objects.QueryResponse;
import dev.brighten.antivpn.web.objects.VPNResponse; import dev.brighten.antivpn.web.objects.VPNResponse;
import java.io.IOException; import java.io.IOException;
public class FunkemunkyAPI { public class FunkemunkyAPI {
/** /**
*
* Queries <a href="https://funkemunky.cc/vpn">...</a> API and returns information on the IP * Queries <a href="https://funkemunky.cc/vpn">...</a> API and returns information on the IP
* *
* @param ip String * @param ip String
@@ -35,40 +33,47 @@ public class FunkemunkyAPI {
* @param cachedResults boolean * @param cachedResults boolean
* @return VPNResponse * @return VPNResponse
* @throws JSONException Throws when JSON response is not formatted properly. * @throws JSONException Throws when JSON response is not formatted properly.
* @throws IOException Throws when there is an error connecting to and processing information from API. * @throws IOException Throws when there is an error connecting to and processing information from
* API.
*/ */
public static VPNResponse getVPNResponse(String ip, String license, boolean cachedResults /* faster if set to true*/) public static VPNResponse getVPNResponse(
String ip, String license, boolean cachedResults /* faster if set to true*/)
throws JSONException, IOException { throws JSONException, IOException {
JSONObject result = JsonReader.readJsonFromUrl(String JSONObject result =
.format("https://funkemunky.cc/vpn?ip=%s&license=%s&cache=%s", JsonReader.readJsonFromUrl(
String.format(
"https://funkemunky.cc/vpn?ip=%s&license=%s&cache=%s",
ip, license.isEmpty() ? "none" : license, cachedResults)); ip, license.isEmpty() ? "none" : license, cachedResults));
return VPNResponse.fromJson(result); return VPNResponse.fromJson(result);
} }
/** /**
* Feeds into {@link FunkemunkyAPI#getQueryResponse(String)} using "none" as argument * Feeds into {@link FunkemunkyAPI#getQueryResponse(String)} using "none" as argument to grab
* to grab query information based on the connecting IP address. * query information based on the connecting IP address.
* *
* @return QueryResponse * @return QueryResponse
* @throws JSONException Throws when JSON response is not formatted properly. * @throws JSONException Throws when JSON response is not formatted properly.
* @throws IOException Throws when there is an error connecting to and processing information from API. * @throws IOException Throws when there is an error connecting to and processing information from
* API.
*/ */
public static QueryResponse getQueryResponse() throws JSONException, IOException { public static QueryResponse getQueryResponse() throws JSONException, IOException {
return getQueryResponse("none"); return getQueryResponse("none");
} }
/** /**
* Queries <a href="https://funkemunky.cc/vpn/queryCheck">...</a> and returns information based on the * Queries <a href="https://funkemunky.cc/vpn/queryCheck">...</a> and returns information based on
* provided licence input. * the provided licence input.
* *
* @param license String * @param license String
* @return QueryResponse * @return QueryResponse
* @throws JSONException Throws when JSON response is not formatted properly. * @throws JSONException Throws when JSON response is not formatted properly.
* @throws IOException Throws when there is an error connecting to and processing information from API. * @throws IOException Throws when there is an error connecting to and processing information from
* API.
*/ */
public static QueryResponse getQueryResponse(String license) throws JSONException, IOException { public static QueryResponse getQueryResponse(String license) throws JSONException, IOException {
JSONObject result = JsonReader.readJsonFromUrl("https://funkemunky.cc/vpn/queryCheck?license=" + license); JSONObject result =
JsonReader.readJsonFromUrl("https://funkemunky.cc/vpn/queryCheck?license=" + license);
return QueryResponse.fromJson(result); return QueryResponse.fromJson(result);
} }
@@ -21,9 +21,9 @@ import dev.brighten.antivpn.utils.json.JSONObject;
import lombok.Builder; import lombok.Builder;
import lombok.Data; import lombok.Data;
/** /**
* Used to format the JSON response from <a href="https://funkemunky.cc/vpn/queryCheck">...</a> into an object for project use. * Used to format the JSON response from <a href="https://funkemunky.cc/vpn/queryCheck">...</a> into
* an object for project use.
*/ */
@Data @Data
@Builder(toBuilder = true) @Builder(toBuilder = true)
@@ -35,7 +35,8 @@ public class QueryResponse {
private long queriesMax; private long queriesMax;
/** /**
* Formats response from <a href="https://funkemunky.cc/vpn/queryCheck">...</a> into {@link QueryResponse} for project use. * Formats response from <a href="https://funkemunky.cc/vpn/queryCheck">...</a> into {@link
* QueryResponse} for project use.
* *
* @param object JSONObject * @param object JSONObject
* @return QueryResponse * @return QueryResponse
@@ -44,13 +45,15 @@ public class QueryResponse {
public static QueryResponse fromJson(JSONObject object) throws JSONException { public static QueryResponse fromJson(JSONObject object) throws JSONException {
boolean validPlan = object.getBoolean("validPlan"); boolean validPlan = object.getBoolean("validPlan");
if(!validPlan) { // Nothing else will be returned from API if validPlan is false. if (!validPlan) { // Nothing else will be returned from API if validPlan is false.
return QueryResponse.builder().validPlan(false).build(); return QueryResponse.builder().validPlan(false).build();
} }
return QueryResponse.builder().validPlan(object.getBoolean("validPlan")) return QueryResponse.builder()
.validPlan(object.getBoolean("validPlan"))
.planType(object.getString("planType")) .planType(object.getString("planType"))
.queries(object.getLong("queries")) .queries(object.getLong("queries"))
.queriesMax(object.getLong("queryLimit")).build(); .queriesMax(object.getLong("queryLimit"))
.build();
} }
} }
@@ -26,7 +26,15 @@ import lombok.Data;
@AllArgsConstructor @AllArgsConstructor
@Builder @Builder
public class VPNResponse { public class VPNResponse {
private String asn, ip, countryName, countryCode, city, timeZone, method, isp, failureReason = "N/A"; private String asn,
ip,
countryName,
countryCode,
city,
timeZone,
method,
isp,
failureReason = "N/A";
private boolean proxy, cached; private boolean proxy, cached;
private final boolean success; private final boolean success;
private double latitude, longitude; private double latitude, longitude;
@@ -52,8 +60,8 @@ public class VPNResponse {
} }
/** /**
* Feeds into {@link VPNResponse#fromJson(JSONObject)} formatting the JSON {@link String} into * Feeds into {@link VPNResponse#fromJson(JSONObject)} formatting the JSON {@link String} into a
* a {@link JSONObject} * {@link JSONObject}
* *
* @param json String * @param json String
* @return VPNResponse * @return VPNResponse
@@ -62,31 +70,41 @@ public class VPNResponse {
return fromJson(new JSONObject(json)); return fromJson(new JSONObject(json));
} }
public static final VPNResponse FAILED_RESPONSE = VPNResponse.builder() public static final VPNResponse FAILED_RESPONSE =
.success(false) VPNResponse.builder().success(false).failureReason("Internal plugin API error.").build();
.failureReason("Internal plugin API error.")
.build();
/** /**
* Formats response from <a href="https://funkemunky.cc/vpn">...</a> into {@link VPNResponse} for project use. * Formats response from <a href="https://funkemunky.cc/vpn">...</a> into {@link VPNResponse} for
* project use.
* *
* @param jsonObject JSONObject * @param jsonObject JSONObject
* @return VPNResponse * @return VPNResponse
* @throws JSONException Throws when JSON is not formatted properly. * @throws JSONException Throws when JSON is not formatted properly.
*/ */
public static VPNResponse fromJson(JSONObject jsonObject) throws JSONException { public static VPNResponse fromJson(JSONObject jsonObject) throws JSONException {
if(jsonObject.getBoolean("success")) { if (jsonObject.getBoolean("success")) {
return new VPNResponse(jsonObject.getString("asn"), jsonObject.getString("ip"), return new VPNResponse(
jsonObject.getString("countryName"), jsonObject.getString("countryCode"), jsonObject.getString("asn"),
jsonObject.getString("city"), jsonObject.getString("timeZone"), jsonObject.getString("ip"),
jsonObject.getString("countryName"),
jsonObject.getString("countryCode"),
jsonObject.getString("city"),
jsonObject.getString("timeZone"),
jsonObject.has("method") ? jsonObject.getString("method") : "N/A", jsonObject.has("method") ? jsonObject.getString("method") : "N/A",
jsonObject.getString("isp"), "N/A", jsonObject.getBoolean("proxy"), jsonObject.getString("isp"),
jsonObject.getBoolean("cached"), jsonObject.getBoolean("success"), "N/A",
jsonObject.getDouble("latitude"), jsonObject.getDouble("longitude"), jsonObject.getBoolean("proxy"),
jsonObject.getLong("lastAccess"), jsonObject.getInt("queriesLeft")); jsonObject.getBoolean("cached"),
jsonObject.getBoolean("success"),
jsonObject.getDouble("latitude"),
jsonObject.getDouble("longitude"),
jsonObject.getLong("lastAccess"),
jsonObject.getInt("queriesLeft"));
} else { } else {
return VPNResponse.builder().success(false) return VPNResponse.builder()
.failureReason(jsonObject.getString("failureReason")).build(); .success(false)
.failureReason(jsonObject.getString("failureReason"))
.build();
} }
} }
} }
@@ -1,18 +1,15 @@
package dev.brighten.antivpn.database; package dev.brighten.antivpn.database;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.lenient;
import static org.mockito.Mockito.when;
import dev.brighten.antivpn.AntiVPN; import dev.brighten.antivpn.AntiVPN;
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.database.sql.utils.MySQL; import dev.brighten.antivpn.database.sql.utils.MySQL;
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.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.io.TempDir;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.io.IOException;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.List; import java.util.List;
@@ -22,21 +19,19 @@ import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
import java.util.logging.Level; import java.util.logging.Level;
import org.junit.jupiter.api.AfterEach;
import static org.junit.jupiter.api.Assertions.*; import org.junit.jupiter.api.BeforeEach;
import static org.mockito.Mockito.lenient; import org.junit.jupiter.api.io.TempDir;
import static org.mockito.Mockito.when; import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
abstract class DatabaseIntegrationTestSupport { abstract class DatabaseIntegrationTestSupport {
@TempDir @TempDir Path pluginFolder;
Path pluginFolder;
@Mock @Mock protected AntiVPN antiVPN;
protected AntiVPN antiVPN;
@Mock @Mock protected VPNConfig vpnConfig;
protected VPNConfig vpnConfig;
protected TestVPNExecutor vpnExecutor; protected TestVPNExecutor vpnExecutor;
private AutoCloseable mocks; private AutoCloseable mocks;
@@ -93,7 +88,8 @@ abstract class DatabaseIntegrationTestSupport {
registerDatabase(database); registerDatabase(database);
database.init(); database.init();
VPNResponse response = VPNResponse.builder() VPNResponse response =
VPNResponse.builder()
.ip("1.2.3.4") .ip("1.2.3.4")
.asn("AS123") .asn("AS123")
.countryName("United States") .countryName("United States")
@@ -112,11 +108,13 @@ abstract class DatabaseIntegrationTestSupport {
assertTrue(storedResponse.get().isProxy()); assertTrue(storedResponse.get().isProxy());
database.deleteResponse(response.getIp()); database.deleteResponse(response.getIp());
awaitCondition(() -> database.getStoredResponse(response.getIp()).isEmpty(), awaitCondition(
() -> database.getStoredResponse(response.getIp()).isEmpty(),
"Expected cached response to be deleted"); "Expected cached response to be deleted");
database.cacheResponse(response); database.cacheResponse(response);
awaitCondition(() -> database.getStoredResponse(response.getIp()).isPresent(), awaitCondition(
() -> database.getStoredResponse(response.getIp()).isPresent(),
"Expected cached response to be restored"); "Expected cached response to be restored");
UUID uuid = UUID.randomUUID(); UUID uuid = UUID.randomUUID();
@@ -126,7 +124,8 @@ abstract class DatabaseIntegrationTestSupport {
List<UUID> whitelisted = database.getAllWhitelisted(); List<UUID> whitelisted = database.getAllWhitelisted();
assertTrue(whitelisted.contains(uuid)); assertTrue(whitelisted.contains(uuid));
database.removeWhitelist(uuid); database.removeWhitelist(uuid);
awaitCondition(() -> !database.isWhitelisted(uuid), "Expected UUID whitelist entry to be removed"); awaitCondition(
() -> !database.isWhitelisted(uuid), "Expected UUID whitelist entry to be removed");
CIDRUtils cidr = new CIDRUtils("192.168.1.0/24"); CIDRUtils cidr = new CIDRUtils("192.168.1.0/24");
assertFalse(database.isWhitelisted(cidr)); assertFalse(database.isWhitelisted(cidr));
@@ -135,7 +134,8 @@ abstract class DatabaseIntegrationTestSupport {
List<CIDRUtils> whitelistedIps = database.getAllWhitelistedIps(); List<CIDRUtils> whitelistedIps = database.getAllWhitelistedIps();
assertTrue(whitelistedIps.stream().anyMatch(entry -> entry.getCidr().equals(cidr.getCidr()))); assertTrue(whitelistedIps.stream().anyMatch(entry -> entry.getCidr().equals(cidr.getCidr())));
database.removeWhitelist(cidr); database.removeWhitelist(cidr);
awaitCondition(() -> !database.isWhitelisted(cidr), "Expected CIDR whitelist entry to be removed"); awaitCondition(
() -> !database.isWhitelisted(cidr), "Expected CIDR whitelist entry to be removed");
database.updateAlertsState(uuid, true); database.updateAlertsState(uuid, true);
awaitCondition(() -> awaitAlertsState(database, uuid), "Expected alerts to be enabled"); awaitCondition(() -> awaitAlertsState(database, uuid), "Expected alerts to be enabled");
@@ -143,24 +143,30 @@ abstract class DatabaseIntegrationTestSupport {
awaitCondition(() -> !awaitAlertsState(database, uuid), "Expected alerts to be disabled"); awaitCondition(() -> !awaitAlertsState(database, uuid), "Expected alerts to be disabled");
database.clearResponses(); database.clearResponses();
awaitCondition(() -> database.getStoredResponse(response.getIp()).isEmpty(), awaitCondition(
() -> database.getStoredResponse(response.getIp()).isEmpty(),
"Expected cached responses to be cleared"); "Expected cached responses to be cleared");
} }
private Optional<VPNResponse> awaitStoredResponse(VPNDatabase database, String ip) throws InterruptedException { private Optional<VPNResponse> awaitStoredResponse(VPNDatabase database, String ip)
throws InterruptedException {
AtomicReference<Optional<VPNResponse>> result = new AtomicReference<>(Optional.empty()); AtomicReference<Optional<VPNResponse>> result = new AtomicReference<>(Optional.empty());
awaitCondition(() -> { awaitCondition(
() -> {
Optional<VPNResponse> response = database.getStoredResponse(ip); Optional<VPNResponse> response = database.getStoredResponse(ip);
result.set(response); result.set(response);
return response.isPresent(); return response.isPresent();
}, "Timed out waiting for cached response"); },
"Timed out waiting for cached response");
return result.get(); return result.get();
} }
private boolean awaitAlertsState(VPNDatabase database, UUID uuid) throws InterruptedException { private boolean awaitAlertsState(VPNDatabase database, UUID uuid) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(1); CountDownLatch latch = new CountDownLatch(1);
AtomicReference<Boolean> result = new AtomicReference<>(false); AtomicReference<Boolean> result = new AtomicReference<>(false);
database.alertsState(uuid, enabled -> { database.alertsState(
uuid,
enabled -> {
result.set(enabled); result.set(enabled);
latch.countDown(); latch.countDown();
}); });
@@ -168,7 +174,8 @@ abstract class DatabaseIntegrationTestSupport {
return result.get(); return result.get();
} }
protected void awaitCondition(CheckedBooleanSupplier condition, String failureMessage) throws InterruptedException { protected void awaitCondition(CheckedBooleanSupplier condition, String failureMessage)
throws InterruptedException {
long deadline = System.nanoTime() + TimeUnit.SECONDS.toNanos(10); long deadline = System.nanoTime() + TimeUnit.SECONDS.toNanos(10);
while (System.nanoTime() < deadline) { while (System.nanoTime() < deadline) {
try { try {
@@ -1,24 +1,22 @@
package dev.brighten.antivpn.database; package dev.brighten.antivpn.database;
import com.mongodb.client.MongoClients;
import dev.brighten.antivpn.database.mongo.MongoVPN;
import org.junit.jupiter.api.Test;
import org.bson.Document;
import org.testcontainers.containers.MongoDBContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import java.util.UUID;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import com.mongodb.client.MongoClients;
import dev.brighten.antivpn.database.mongo.MongoVPN;
import java.util.UUID;
import org.bson.Document;
import org.junit.jupiter.api.Test;
import org.testcontainers.containers.MongoDBContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
@Testcontainers @Testcontainers
class MongoDatabaseIntegrationTest extends DatabaseIntegrationTestSupport { class MongoDatabaseIntegrationTest extends DatabaseIntegrationTestSupport {
@Container @Container private static final MongoDBContainer MONGO = new MongoDBContainer("mongo:6.0.14");
private static final MongoDBContainer MONGO = new MongoDBContainer("mongo:6.0.14");
@Test @Test
void mongoDatabaseImplementsTheVpnDatabaseContract() throws Exception { void mongoDatabaseImplementsTheVpnDatabaseContract() throws Exception {
@@ -31,7 +29,8 @@ class MongoDatabaseIntegrationTest extends DatabaseIntegrationTestSupport {
when(vpnConfig.getIp()).thenReturn(MONGO.getHost()); when(vpnConfig.getIp()).thenReturn(MONGO.getHost());
when(vpnConfig.getPort()).thenReturn(MONGO.getMappedPort(27017)); when(vpnConfig.getPort()).thenReturn(MONGO.getMappedPort(27017));
when(vpnConfig.getDatabaseName()).thenReturn("antivpn_" + UUID.randomUUID().toString().replace("-", "")); when(vpnConfig.getDatabaseName())
.thenReturn("antivpn_" + UUID.randomUUID().toString().replace("-", ""));
when(vpnConfig.mongoDatabaseURL()).thenReturn(""); when(vpnConfig.mongoDatabaseURL()).thenReturn("");
when(vpnConfig.useDatabaseCreds()).thenReturn(false); when(vpnConfig.useDatabaseCreds()).thenReturn(false);
@@ -1,21 +1,22 @@
package dev.brighten.antivpn.database; package dev.brighten.antivpn.database;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.when;
import dev.brighten.antivpn.database.sql.MySqlVPN; import dev.brighten.antivpn.database.sql.MySqlVPN;
import java.sql.DriverManager;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.testcontainers.containers.MySQLContainer; import org.testcontainers.containers.MySQLContainer;
import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers; import org.testcontainers.junit.jupiter.Testcontainers;
import java.sql.DriverManager;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.when;
@Testcontainers @Testcontainers
class MySqlDatabaseIntegrationTest extends DatabaseIntegrationTestSupport { class MySqlDatabaseIntegrationTest extends DatabaseIntegrationTestSupport {
@Container @Container
private static final MySQLContainer<?> MYSQL = new MySQLContainer<>("mysql:8.0.36") private static final MySQLContainer<?> MYSQL =
new MySQLContainer<>("mysql:8.0.36")
.withDatabaseName("antivpn") .withDatabaseName("antivpn")
.withUsername("testuser") .withUsername("testuser")
.withPassword("testpass"); .withPassword("testpass");
@@ -24,7 +25,9 @@ class MySqlDatabaseIntegrationTest extends DatabaseIntegrationTestSupport {
void mysqlDatabaseImplementsTheVpnDatabaseContract() throws Exception { void mysqlDatabaseImplementsTheVpnDatabaseContract() throws Exception {
assertTrue(MYSQL.isRunning(), "MySQL Testcontainer should be running"); assertTrue(MYSQL.isRunning(), "MySQL Testcontainer should be running");
try (var connection = DriverManager.getConnection(MYSQL.getJdbcUrl(), MYSQL.getUsername(), MYSQL.getPassword()); try (var connection =
DriverManager.getConnection(
MYSQL.getJdbcUrl(), MYSQL.getUsername(), MYSQL.getPassword());
var statement = connection.createStatement(); var statement = connection.createStatement();
var resultSet = statement.executeQuery("SELECT 1")) { var resultSet = statement.executeQuery("SELECT 1")) {
assertTrue(resultSet.next(), "Expected a row from the MySQL container"); assertTrue(resultSet.next(), "Expected a row from the MySQL container");
@@ -32,7 +35,8 @@ class MySqlDatabaseIntegrationTest extends DatabaseIntegrationTestSupport {
} }
var pingResult = MYSQL.execInContainer("mysqladmin", "ping", "-h", "127.0.0.1", "-ptestpass"); var pingResult = MYSQL.execInContainer("mysqladmin", "ping", "-h", "127.0.0.1", "-ptestpass");
assertEquals(0, pingResult.getExitCode(), "Expected mysqladmin ping to succeed inside the container"); assertEquals(
0, pingResult.getExitCode(), "Expected mysqladmin ping to succeed inside the container");
when(vpnConfig.getIp()).thenReturn(MYSQL.getHost()); when(vpnConfig.getIp()).thenReturn(MYSQL.getHost());
when(vpnConfig.getPort()).thenReturn(MYSQL.getMappedPort(3306)); when(vpnConfig.getPort()).thenReturn(MYSQL.getMappedPort(3306));
@@ -1,5 +1,12 @@
package dev.brighten.antivpn.utils; package dev.brighten.antivpn.utils;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import com.sun.net.httpserver.HttpExchange; import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpServer; import com.sun.net.httpserver.HttpServer;
import dev.brighten.antivpn.AntiVPN; import dev.brighten.antivpn.AntiVPN;
@@ -9,12 +16,6 @@ import dev.brighten.antivpn.api.VPNExecutor;
import dev.brighten.antivpn.command.CommandExecutor; import dev.brighten.antivpn.command.CommandExecutor;
import dev.brighten.antivpn.command.impl.AllowlistCommand; import dev.brighten.antivpn.command.impl.AllowlistCommand;
import dev.brighten.antivpn.database.VPNDatabase; import dev.brighten.antivpn.database.VPNDatabase;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream; import java.io.OutputStream;
import java.lang.reflect.Field; import java.lang.reflect.Field;
@@ -25,32 +26,26 @@ import java.util.Optional;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level; import java.util.logging.Level;
import org.junit.jupiter.api.AfterEach;
import static org.junit.jupiter.api.Assertions.assertEquals; import org.junit.jupiter.api.BeforeEach;
import static org.junit.jupiter.api.Assertions.assertTrue; import org.junit.jupiter.api.Test;
import static org.mockito.Mockito.mock; import org.mockito.Mock;
import static org.mockito.Mockito.never; import org.mockito.MockitoAnnotations;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
class AllowlistCommandTest { class AllowlistCommandTest {
private static final UUID FUNKEMUNKY_UUID = UUID.fromString("123e4567-e89b-12d3-a456-426614174000"); private static final UUID FUNKEMUNKY_UUID =
UUID.fromString("123e4567-e89b-12d3-a456-426614174000");
@Mock @Mock private AntiVPN antiVPN;
private AntiVPN antiVPN;
@Mock @Mock private VPNConfig vpnConfig;
private VPNConfig vpnConfig;
@Mock @Mock private VPNDatabase database;
private VPNDatabase database;
@Mock @Mock private dev.brighten.antivpn.api.PlayerExecutor playerExecutor;
private dev.brighten.antivpn.api.PlayerExecutor playerExecutor;
@Mock @Mock private CommandExecutor commandExecutor;
private CommandExecutor commandExecutor;
private TestVPNExecutor vpnExecutor; private TestVPNExecutor vpnExecutor;
private AutoCloseable mocks; private AutoCloseable mocks;
@@ -111,7 +106,9 @@ class AllowlistCommandTest {
AtomicInteger mojangHits = new AtomicInteger(); AtomicInteger mojangHits = new AtomicInteger();
HttpServer mojangServer = HttpServer.create(new InetSocketAddress("127.0.0.1", 0), 0); HttpServer mojangServer = HttpServer.create(new InetSocketAddress("127.0.0.1", 0), 0);
mojangServer.createContext("/users/profiles/minecraft/funkemunky", exchange -> { mojangServer.createContext(
"/users/profiles/minecraft/funkemunky",
exchange -> {
mojangHits.incrementAndGet(); mojangHits.incrementAndGet();
respond(exchange); respond(exchange);
}); });
@@ -120,8 +117,7 @@ class AllowlistCommandTest {
try { try {
MiscUtils.setLookupEndpointsForTesting( MiscUtils.setLookupEndpointsForTesting(
"http://127.0.0.1:" + closedPort + "/mojang/uuid?name=", "http://127.0.0.1:" + closedPort + "/mojang/uuid?name=",
"http://127.0.0.1:" + mojangServer.getAddress().getPort() + "/users/profiles/minecraft/" "http://127.0.0.1:" + mojangServer.getAddress().getPort() + "/users/profiles/minecraft/");
);
String result = command.execute(commandExecutor, new String[] {"add", "funkemunky"}); String result = command.execute(commandExecutor, new String[] {"add", "funkemunky"});
@@ -138,7 +134,8 @@ class AllowlistCommandTest {
byte[] bytes = "{\"id\":\"123e4567e89b12d3a456426614174000\"}".getBytes(StandardCharsets.UTF_8); byte[] bytes = "{\"id\":\"123e4567e89b12d3a456426614174000\"}".getBytes(StandardCharsets.UTF_8);
exchange.getResponseHeaders().add("Content-Type", "application/json"); exchange.getResponseHeaders().add("Content-Type", "application/json");
exchange.sendResponseHeaders(200, bytes.length); exchange.sendResponseHeaders(200, bytes.length);
try (exchange; OutputStream outputStream = exchange.getResponseBody()) { try (exchange;
OutputStream outputStream = exchange.getResponseBody()) {
outputStream.write(bytes); outputStream.write(bytes);
} }
} }

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