Add a 500ms rate limit to disguise commands to try prevent possible crashes. Fixes #551

This commit is contained in:
libraryaddict 2021-05-26 06:31:08 +12:00
parent 135b0fdeaa
commit 54b9490604
7 changed files with 63 additions and 27 deletions

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -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;

View File

@ -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) {

View File

@ -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<>();

View File

@ -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."),

View File

@ -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