Add a 500ms rate limit to disguise commands to try prevent possible crashes. Fixes #551
This commit is contained in:
		| @@ -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<Class<? extends DisguiseBaseCommand>, String> disguiseCommands; | ||||
|     private final Cache<UUID, Long> rateLimit = CacheBuilder.newBuilder().expireAfterWrite(500, TimeUnit.MILLISECONDS).build(); | ||||
|  | ||||
|     static { | ||||
|         HashMap<Class<? extends DisguiseBaseCommand>, 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<String> getTabDisguiseTypes(CommandSender sender, DisguisePermissions perms, String[] allArgs, | ||||
|             int startsAt, String currentArg) { | ||||
|     protected List<String> 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<String> 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<String> getTabDisguiseOptions(CommandSender commandSender, DisguisePermissions perms, | ||||
|             DisguisePerm disguisePerm, String[] allArgs, int startsAt, String currentArg) { | ||||
|     protected List<String> getTabDisguiseOptions(CommandSender commandSender, DisguisePermissions perms, DisguisePerm disguisePerm, String[] allArgs, | ||||
|                                                  int startsAt, String currentArg) { | ||||
|         ArrayList<String> 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<String> getTabDisguiseSubOptions(CommandSender commandSender, DisguisePermissions perms, | ||||
|             DisguisePerm disguisePerm, String[] allArgs, int startsAt, String currentArg) { | ||||
|     protected List<String> getTabDisguiseSubOptions(CommandSender commandSender, DisguisePermissions perms, DisguisePerm disguisePerm, String[] allArgs, | ||||
|                                                     int startsAt, String currentArg) { | ||||
|         boolean addMethods = true; | ||||
|         List<String> tabs = new ArrayList<>(); | ||||
|  | ||||
| @@ -199,8 +214,9 @@ public abstract class DisguiseBaseCommand implements CommandExecutor { | ||||
|     } | ||||
|  | ||||
|     protected List<String> filterTabs(List<String> list, String[] origArgs) { | ||||
|         if (origArgs.length == 0) | ||||
|         if (origArgs.length == 0) { | ||||
|             return list; | ||||
|         } | ||||
|  | ||||
|         Iterator<String> 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<String> 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; | ||||
|         } | ||||
|     } | ||||
|   | ||||
| @@ -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; | ||||
|         } | ||||
|   | ||||
| @@ -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; | ||||
|  | ||||
|   | ||||
| @@ -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) { | ||||
|   | ||||
| @@ -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<String> classes = new ArrayList<>(); | ||||
|  | ||||
|   | ||||
| @@ -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."), | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
		Reference in New Issue
	
	Block a user