From 54b9490604a8ed69bdfd64a694bdf4692166e7b2 Mon Sep 17 00:00:00 2001 From: libraryaddict Date: Wed, 26 May 2021 06:31:08 +1200 Subject: [PATCH] Add a 500ms rate limit to disguise commands to try prevent possible crashes. Fixes #551 --- .../commands/DisguiseBaseCommand.java | 54 ++++++++++++------- .../commands/disguise/DisguiseCommand.java | 21 ++++---- .../disguise/DisguiseEntityCommand.java | 4 ++ .../disguise/DisguisePlayerCommand.java | 4 ++ .../disguise/DisguiseRadiusCommand.java | 4 ++ .../utilities/translations/LibsMsg.java | 1 + src/main/resources/plugin.yml | 2 + 7 files changed, 63 insertions(+), 27 deletions(-) diff --git a/src/main/java/me/libraryaddict/disguise/commands/DisguiseBaseCommand.java b/src/main/java/me/libraryaddict/disguise/commands/DisguiseBaseCommand.java index 7bb936b8..9d1d832b 100644 --- a/src/main/java/me/libraryaddict/disguise/commands/DisguiseBaseCommand.java +++ b/src/main/java/me/libraryaddict/disguise/commands/DisguiseBaseCommand.java @@ -1,6 +1,8 @@ package me.libraryaddict.disguise.commands; import com.comphenix.protocol.ProtocolLibrary; +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; import me.libraryaddict.disguise.DisguiseConfig; import me.libraryaddict.disguise.commands.disguise.DisguiseCommand; import me.libraryaddict.disguise.commands.disguise.DisguiseEntityCommand; @@ -19,6 +21,7 @@ import me.libraryaddict.disguise.utilities.parser.DisguiseParser; import me.libraryaddict.disguise.utilities.parser.DisguisePerm; import me.libraryaddict.disguise.utilities.parser.DisguisePermissions; import me.libraryaddict.disguise.utilities.parser.WatcherMethod; +import me.libraryaddict.disguise.utilities.translations.LibsMsg; import org.bukkit.Bukkit; import org.bukkit.ChatColor; import org.bukkit.command.CommandExecutor; @@ -27,12 +30,14 @@ import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; import java.util.*; +import java.util.concurrent.TimeUnit; /** * @author libraryaddict */ public abstract class DisguiseBaseCommand implements CommandExecutor { private static final Map, String> disguiseCommands; + private final Cache rateLimit = CacheBuilder.newBuilder().expireAfterWrite(500, TimeUnit.MILLISECONDS).build(); static { HashMap, String> map = new HashMap<>(); @@ -49,6 +54,20 @@ public abstract class DisguiseBaseCommand implements CommandExecutor { disguiseCommands = map; } + protected boolean hasHitRateLimit(CommandSender sender) { + if (sender.isOp() || !(sender instanceof Player) || sender.hasPermission("libsdisguises.ratelimitbypass")) { + return false; + } + + if (rateLimit.getIfPresent(((Player) sender).getUniqueId()) != null) { + LibsMsg.TOO_FAST.send(sender); + return true; + } + + rateLimit.put(((Player) sender).getUniqueId(), System.currentTimeMillis()); + return false; + } + protected boolean isNotPremium(CommandSender sender) { String requiredProtocolLib = DisguiseUtilities.getProtocolLibRequiredVersion(); String version = ProtocolLibrary.getPlugin().getDescription().getVersion(); @@ -59,8 +78,7 @@ public abstract class DisguiseBaseCommand implements CommandExecutor { if (sender instanceof Player && !sender.isOp() && (!LibsPremium.isPremium() || LibsPremium.getPaidInformation() == LibsPremium.getPluginInformation())) { - sender.sendMessage(ChatColor.RED + - "This is the free version of Lib's Disguises, player commands are limited to console and " + + sender.sendMessage(ChatColor.RED + "This is the free version of Lib's Disguises, player commands are limited to console and " + "Operators only! Purchase the plugin for non-admin usage!"); return true; } @@ -68,8 +86,7 @@ public abstract class DisguiseBaseCommand implements CommandExecutor { return false; } - protected List getTabDisguiseTypes(CommandSender sender, DisguisePermissions perms, String[] allArgs, - int startsAt, String currentArg) { + protected List getTabDisguiseTypes(CommandSender sender, DisguisePermissions perms, String[] allArgs, int startsAt, String currentArg) { // If not enough arguments to get current disguise type if (allArgs.length <= startsAt) { return getAllowedDisguises(perms); @@ -85,8 +102,7 @@ public abstract class DisguiseBaseCommand implements CommandExecutor { // If current argument is just after the disguise type, and disguise type is a player which is not a custom // disguise - if (allArgs.length == startsAt + 1 && disguiseType.getType() == DisguiseType.PLAYER && - !disguiseType.isCustomDisguise()) { + if (allArgs.length == startsAt + 1 && disguiseType.getType() == DisguiseType.PLAYER && !disguiseType.isCustomDisguise()) { ArrayList tabs = new ArrayList<>(); // Add all player names to tab list @@ -103,8 +119,7 @@ public abstract class DisguiseBaseCommand implements CommandExecutor { return tabs; } - return getTabDisguiseOptions(sender, perms, disguiseType, allArgs, startsAt + (disguiseType.isPlayer() ? 2 : 1), - currentArg); + return getTabDisguiseOptions(sender, perms, disguiseType, allArgs, startsAt + (disguiseType.isPlayer() ? 2 : 1), currentArg); } /** @@ -114,8 +129,8 @@ public abstract class DisguiseBaseCommand implements CommandExecutor { * @param startsAt What index this starts at * @return a list of viable disguise options */ - protected List getTabDisguiseOptions(CommandSender commandSender, DisguisePermissions perms, - DisguisePerm disguisePerm, String[] allArgs, int startsAt, String currentArg) { + protected List getTabDisguiseOptions(CommandSender commandSender, DisguisePermissions perms, DisguisePerm disguisePerm, String[] allArgs, + int startsAt, String currentArg) { ArrayList usedOptions = new ArrayList<>(); WatcherMethod[] methods = ParamInfoManager.getDisguiseWatcherMethods(disguisePerm.getWatcherClass()); @@ -142,8 +157,8 @@ public abstract class DisguiseBaseCommand implements CommandExecutor { return getTabDisguiseSubOptions(commandSender, perms, disguisePerm, allArgs, startsAt, currentArg); } - protected List getTabDisguiseSubOptions(CommandSender commandSender, DisguisePermissions perms, - DisguisePerm disguisePerm, String[] allArgs, int startsAt, String currentArg) { + protected List getTabDisguiseSubOptions(CommandSender commandSender, DisguisePermissions perms, DisguisePerm disguisePerm, String[] allArgs, + int startsAt, String currentArg) { boolean addMethods = true; List tabs = new ArrayList<>(); @@ -199,8 +214,9 @@ public abstract class DisguiseBaseCommand implements CommandExecutor { } protected List filterTabs(List list, String[] origArgs) { - if (origArgs.length == 0) + if (origArgs.length == 0) { return list; + } Iterator itel = list.iterator(); String label = origArgs[origArgs.length - 1].toLowerCase(Locale.ENGLISH); @@ -208,8 +224,9 @@ public abstract class DisguiseBaseCommand implements CommandExecutor { while (itel.hasNext()) { String name = itel.next(); - if (name.toLowerCase(Locale.ENGLISH).startsWith(label)) + if (name.toLowerCase(Locale.ENGLISH).startsWith(label)) { continue; + } itel.remove(); } @@ -231,8 +248,9 @@ public abstract class DisguiseBaseCommand implements CommandExecutor { ArrayList allowedDisguises = new ArrayList<>(); for (DisguisePerm type : permissions.getAllowed()) { - if (type.isUnknown()) + if (type.isUnknown()) { continue; + } allowedDisguises.add(type.toReadable().replaceAll(" ", "_")); } @@ -250,8 +268,9 @@ public abstract class DisguiseBaseCommand implements CommandExecutor { for (int i = 0; i < args.length - 1; i++) { String s = args[i]; - if (s.trim().isEmpty()) + if (s.trim().isEmpty()) { continue; + } newArgs.add(s); } @@ -289,8 +308,7 @@ public abstract class DisguiseBaseCommand implements CommandExecutor { try { Integer.parseInt(string); return true; - } - catch (Exception ex) { + } catch (Exception ex) { return false; } } diff --git a/src/main/java/me/libraryaddict/disguise/commands/disguise/DisguiseCommand.java b/src/main/java/me/libraryaddict/disguise/commands/disguise/DisguiseCommand.java index e590c993..c0d50bf3 100644 --- a/src/main/java/me/libraryaddict/disguise/commands/disguise/DisguiseCommand.java +++ b/src/main/java/me/libraryaddict/disguise/commands/disguise/DisguiseCommand.java @@ -37,20 +37,22 @@ public class DisguiseCommand extends DisguiseBaseCommand implements TabCompleter return true; } + if (hasHitRateLimit(sender)) { + return true; + } + Disguise disguise; try { - disguise = DisguiseParser.parseDisguise(sender, (Entity) sender, getPermNode(), - DisguiseUtilities.split(StringUtils.join(args, " ")), getPermissions(sender)); - } - catch (DisguiseParseException ex) { + disguise = DisguiseParser + .parseDisguise(sender, (Entity) sender, getPermNode(), DisguiseUtilities.split(StringUtils.join(args, " ")), getPermissions(sender)); + } catch (DisguiseParseException ex) { if (ex.getMessage() != null) { DisguiseUtilities.sendMessage(sender, ex.getMessage()); } return true; - } - catch (Throwable ex) { + } catch (Throwable ex) { ex.printStackTrace(); return true; } @@ -69,9 +71,9 @@ public class DisguiseCommand extends DisguiseBaseCommand implements TabCompleter if (!setViewDisguise(args)) { // They prefer to have the opposite of whatever the view disguises option is - if (DisguiseAPI.hasSelfDisguisePreference(disguise.getEntity()) && - disguise.isSelfDisguiseVisible() == DisguiseConfig.isViewDisguises()) + if (DisguiseAPI.hasSelfDisguisePreference(disguise.getEntity()) && disguise.isSelfDisguiseVisible() == DisguiseConfig.isViewDisguises()) { disguise.setViewSelfDisguise(!disguise.isSelfDisguiseVisible()); + } } if (!DisguiseAPI.isActionBarShown(disguise.getEntity())) { @@ -91,8 +93,9 @@ public class DisguiseCommand extends DisguiseBaseCommand implements TabCompleter private boolean setViewDisguise(String[] strings) { for (String string : strings) { - if (!string.equalsIgnoreCase("setSelfDisguiseVisible")) + if (!string.equalsIgnoreCase("setSelfDisguiseVisible")) { continue; + } return true; } diff --git a/src/main/java/me/libraryaddict/disguise/commands/disguise/DisguiseEntityCommand.java b/src/main/java/me/libraryaddict/disguise/commands/disguise/DisguiseEntityCommand.java index a1cb193f..5ce66101 100644 --- a/src/main/java/me/libraryaddict/disguise/commands/disguise/DisguiseEntityCommand.java +++ b/src/main/java/me/libraryaddict/disguise/commands/disguise/DisguiseEntityCommand.java @@ -41,6 +41,10 @@ public class DisguiseEntityCommand extends DisguiseBaseCommand implements TabCom return true; } + if (hasHitRateLimit(sender)) { + return true; + } + String[] disguiseArgs = DisguiseUtilities.split(StringUtils.join(args, " ")); Disguise testDisguise; diff --git a/src/main/java/me/libraryaddict/disguise/commands/disguise/DisguisePlayerCommand.java b/src/main/java/me/libraryaddict/disguise/commands/disguise/DisguisePlayerCommand.java index e8f92110..1a5b38e5 100644 --- a/src/main/java/me/libraryaddict/disguise/commands/disguise/DisguisePlayerCommand.java +++ b/src/main/java/me/libraryaddict/disguise/commands/disguise/DisguisePlayerCommand.java @@ -48,6 +48,10 @@ public class DisguisePlayerCommand extends DisguiseBaseCommand implements TabCom return true; } + if (hasHitRateLimit(sender)) { + return true; + } + Entity entityTarget = Bukkit.getPlayer(args[0]); if (entityTarget == null) { diff --git a/src/main/java/me/libraryaddict/disguise/commands/disguise/DisguiseRadiusCommand.java b/src/main/java/me/libraryaddict/disguise/commands/disguise/DisguiseRadiusCommand.java index 37a01717..5dedc0b4 100644 --- a/src/main/java/me/libraryaddict/disguise/commands/disguise/DisguiseRadiusCommand.java +++ b/src/main/java/me/libraryaddict/disguise/commands/disguise/DisguiseRadiusCommand.java @@ -66,6 +66,10 @@ public class DisguiseRadiusCommand extends DisguiseBaseCommand implements TabCom return true; } + if (hasHitRateLimit(sender)) { + return true; + } + if (args[0].equalsIgnoreCase(TranslateType.DISGUISES.get("EntityType")) || args[0].equalsIgnoreCase(TranslateType.DISGUISES.get("EntityType") + "s")) { ArrayList classes = new ArrayList<>(); diff --git a/src/main/java/me/libraryaddict/disguise/utilities/translations/LibsMsg.java b/src/main/java/me/libraryaddict/disguise/utilities/translations/LibsMsg.java index 76373660..667bd300 100644 --- a/src/main/java/me/libraryaddict/disguise/utilities/translations/LibsMsg.java +++ b/src/main/java/me/libraryaddict/disguise/utilities/translations/LibsMsg.java @@ -125,6 +125,7 @@ public enum LibsMsg { MADE_REF(ChatColor.RED + "Constructed a %s disguise! Your reference is %s"), MADE_REF_EXAMPLE(ChatColor.RED + "Example usage: /disguise %s"), NO_CONSOLE(ChatColor.RED + "You may not use this command from the console!"), + TOO_FAST(ChatColor.RED + "You are using the disguise command too fast!"), NO_MODS(ChatColor.RED + "%s is not using any mods!"), MODS_LIST(ChatColor.DARK_GREEN + "%s has the mods:" + ChatColor.AQUA + " %s"), NO_PERM(ChatColor.RED + "You are forbidden to use this command."), diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 9a2eef43..03ba07fe 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -137,6 +137,8 @@ permissions: description: Allows the command user to set names on different heights libsdisguises.grabhead: description: Allows the command user to use /grabhead + libsdisguises.ratelimitbypass: + description: Allows a living player to bypass the 500ms rate limit on disguise commands, used to prevent crashes libsdisguises.seecmd: description: See all commands in tab-completion default: true