From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: Nassim Jahnke Date: Mon, 5 Feb 2024 11:54:04 +0100 Subject: [PATCH] Improve tag parser handling diff --git a/src/main/java/com/mojang/brigadier/CommandDispatcher.java b/src/main/java/com/mojang/brigadier/CommandDispatcher.java index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/com/mojang/brigadier/CommandDispatcher.java +++ b/src/main/java/com/mojang/brigadier/CommandDispatcher.java @@ -0,0 +0,0 @@ public class CommandDispatcher { } final CommandContextBuilder context = contextSoFar.copy(); final StringReader reader = new StringReader(originalReader); + boolean stop = false; // Paper - Handle non-recoverable exceptions try { try { child.parse(reader, context); + // Paper start - Handle non-recoverable exceptions + } catch (final io.papermc.paper.brigadier.TagParseCommandSyntaxException e) { + stop = true; + throw e; + // Paper end - Handle non-recoverable exceptions } catch (final RuntimeException ex) { throw CommandSyntaxException.BUILT_IN_EXCEPTIONS.dispatcherParseException().createWithContext(reader, ex.getMessage()); } @@ -0,0 +0,0 @@ public class CommandDispatcher { } errors.put(child, ex); reader.setCursor(cursor); + if (stop) return new ParseResults<>(contextSoFar, originalReader, errors); // Paper - Handle non-recoverable exceptions continue; } diff --git a/src/main/java/io/papermc/paper/brigadier/TagParseCommandSyntaxException.java b/src/main/java/io/papermc/paper/brigadier/TagParseCommandSyntaxException.java new file mode 100644 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 --- /dev/null +++ b/src/main/java/io/papermc/paper/brigadier/TagParseCommandSyntaxException.java @@ -0,0 +0,0 @@ +package io.papermc.paper.brigadier; + +import com.mojang.brigadier.LiteralMessage; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.mojang.brigadier.exceptions.SimpleCommandExceptionType; +import net.minecraft.network.chat.Component; + +public final class TagParseCommandSyntaxException extends CommandSyntaxException { + + private static final SimpleCommandExceptionType EXCEPTION_TYPE = new SimpleCommandExceptionType(new LiteralMessage("Error parsing NBT")); + + public TagParseCommandSyntaxException(final String message) { + super(EXCEPTION_TYPE, Component.literal(message)); + } +} diff --git a/src/main/java/net/minecraft/commands/arguments/selector/SelectorPattern.java b/src/main/java/net/minecraft/commands/arguments/selector/SelectorPattern.java index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/net/minecraft/commands/arguments/selector/SelectorPattern.java +++ b/src/main/java/net/minecraft/commands/arguments/selector/SelectorPattern.java @@ -0,0 +0,0 @@ public record SelectorPattern(String pattern, EntitySelector resolved) { EntitySelectorParser entitySelectorParser = new EntitySelectorParser(new StringReader(selector), true); return DataResult.success(new SelectorPattern(selector, entitySelectorParser.parse())); } catch (CommandSyntaxException var2) { - return DataResult.error(() -> "Invalid selector component: " + selector + ": " + var2.getMessage()); + return DataResult.error(() -> "Invalid selector component"); // Paper - limit selector error message } } diff --git a/src/main/java/net/minecraft/nbt/TagParser.java b/src/main/java/net/minecraft/nbt/TagParser.java index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/net/minecraft/nbt/TagParser.java +++ b/src/main/java/net/minecraft/nbt/TagParser.java @@ -0,0 +0,0 @@ public class TagParser { }, CompoundTag::toString); public static final Codec LENIENT_CODEC = Codec.withAlternative(AS_CODEC, CompoundTag.CODEC); private final StringReader reader; + private int depth; // Paper public static CompoundTag parseTag(String string) throws CommandSyntaxException { return new TagParser(new StringReader(string)).readSingleStruct(); @@ -0,0 +0,0 @@ public class TagParser { public CompoundTag readStruct() throws CommandSyntaxException { this.expect('{'); + this.increaseDepth(); // Paper CompoundTag compoundTag = new CompoundTag(); this.reader.skipWhitespace(); @@ -0,0 +0,0 @@ public class TagParser { } this.expect('}'); + this.depth--; // Paper return compoundTag; } @@ -0,0 +0,0 @@ public class TagParser { if (!this.reader.canRead()) { throw ERROR_EXPECTED_VALUE.createWithContext(this.reader); } else { + this.increaseDepth(); // Paper ListTag listTag = new ListTag(); TagType tagType = null; @@ -0,0 +0,0 @@ public class TagParser { } this.expect(']'); + this.depth--; // Paper return listTag; } } @@ -0,0 +0,0 @@ public class TagParser { this.reader.skipWhitespace(); this.reader.expect(c); } + + private void increaseDepth() throws CommandSyntaxException { + this.depth++; + if (this.depth > 512) { + throw new io.papermc.paper.brigadier.TagParseCommandSyntaxException("NBT tag is too complex, depth > 512"); + } + } } diff --git a/src/main/java/net/minecraft/network/chat/ComponentUtils.java b/src/main/java/net/minecraft/network/chat/ComponentUtils.java index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/net/minecraft/network/chat/ComponentUtils.java +++ b/src/main/java/net/minecraft/network/chat/ComponentUtils.java @@ -0,0 +0,0 @@ public class ComponentUtils { } } + @io.papermc.paper.annotation.DoNotUse // Paper - validate separators - right now this method is only used for separator evaluation. Error on build if this changes to re-evaluate. public static Optional updateForEntity(@Nullable CommandSourceStack source, Optional text, @Nullable Entity sender, int depth) throws CommandSyntaxException { return text.isPresent() ? Optional.of(updateForEntity(source, text.get(), sender, depth)) : Optional.empty(); } + // Paper start - validate separator + public static Optional updateSeparatorForEntity(@Nullable CommandSourceStack source, Optional text, @Nullable Entity sender, int depth) throws CommandSyntaxException { + if (text.isEmpty() || !isValidSelector(text.get())) return Optional.empty(); + return Optional.of(updateForEntity(source, text.get(), sender, depth)); + } + public static boolean isValidSelector(final Component component) { + final ComponentContents contents = component.getContents(); + + if (contents instanceof net.minecraft.network.chat.contents.NbtContents || contents instanceof net.minecraft.network.chat.contents.SelectorContents) return false; + if (contents instanceof final net.minecraft.network.chat.contents.TranslatableContents translatableContents) { + for (final Object arg : translatableContents.getArgs()) { + if (arg instanceof final Component argumentAsComponent && !isValidSelector(argumentAsComponent)) return false; + } + } + + return true; + } + // Paper end - validate separator + public static MutableComponent updateForEntity(@Nullable CommandSourceStack source, Component text, @Nullable Entity sender, int depth) throws CommandSyntaxException { if (depth > 100) { return text.copy(); diff --git a/src/main/java/net/minecraft/network/chat/contents/NbtContents.java b/src/main/java/net/minecraft/network/chat/contents/NbtContents.java index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/net/minecraft/network/chat/contents/NbtContents.java +++ b/src/main/java/net/minecraft/network/chat/contents/NbtContents.java @@ -0,0 +0,0 @@ public class NbtContents implements ComponentContents { }).map(Tag::getAsString); if (this.interpreting) { Component component = DataFixUtils.orElse( - ComponentUtils.updateForEntity(source, this.separator, sender, depth), ComponentUtils.DEFAULT_NO_STYLE_SEPARATOR + ComponentUtils.updateSeparatorForEntity(source, this.separator, sender, depth), ComponentUtils.DEFAULT_NO_STYLE_SEPARATOR // Paper - validate separator ); return stream.flatMap(text -> { try { @@ -0,0 +0,0 @@ public class NbtContents implements ComponentContents { } }).reduce((accumulator, current) -> accumulator.append(component).append(current)).orElseGet(Component::empty); } else { - return ComponentUtils.updateForEntity(source, this.separator, sender, depth) + return ComponentUtils.updateSeparatorForEntity(source, this.separator, sender, depth) // Paper - validate separator .map( text -> stream.map(Component::literal) .reduce((accumulator, current) -> accumulator.append(text).append(current)) diff --git a/src/main/java/net/minecraft/network/chat/contents/SelectorContents.java b/src/main/java/net/minecraft/network/chat/contents/SelectorContents.java index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/net/minecraft/network/chat/contents/SelectorContents.java +++ b/src/main/java/net/minecraft/network/chat/contents/SelectorContents.java @@ -0,0 +0,0 @@ public record SelectorContents(SelectorPattern selector, Optional sep if (source == null) { return Component.empty(); } else { - Optional optional = ComponentUtils.updateForEntity(source, this.separator, sender, depth); + Optional optional = ComponentUtils.updateSeparatorForEntity(source, this.separator, sender, depth); // Paper - validate separator return ComponentUtils.formatList(this.selector.resolved().findEntities(source), optional, Entity::getDisplayName); } } diff --git a/src/main/java/net/minecraft/network/chat/contents/TranslatableContents.java b/src/main/java/net/minecraft/network/chat/contents/TranslatableContents.java index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/net/minecraft/network/chat/contents/TranslatableContents.java +++ b/src/main/java/net/minecraft/network/chat/contents/TranslatableContents.java @@ -0,0 +0,0 @@ public class TranslatableContents implements ComponentContents { @Override public Optional visit(FormattedText.ContentConsumer visitor) { + // Paper start - Count visited parts + try { + return this.visit(new TranslatableContentConsumer<>(visitor)); + } catch (IllegalArgumentException ignored) { + return visitor.accept("..."); + } + } + private Optional visit(TranslatableContentConsumer visitor) { + // Paper end - Count visited parts this.decompose(); for (FormattedText formattedText : this.decomposedParts) { @@ -0,0 +0,0 @@ public class TranslatableContents implements ComponentContents { return Optional.empty(); } + // Paper start - Count visited parts + private static final class TranslatableContentConsumer implements FormattedText.ContentConsumer { + private static final IllegalArgumentException EX = new IllegalArgumentException("Too long"); + private final FormattedText.ContentConsumer visitor; + private int visited; + + private TranslatableContentConsumer(FormattedText.ContentConsumer visitor) { + this.visitor = visitor; + } + + @Override + public Optional accept(final String asString) { + if (visited++ > 32) { + throw EX; + } + return this.visitor.accept(asString); + } + } + // Paper end - Count visited parts @Override public MutableComponent resolve(@Nullable CommandSourceStack source, @Nullable Entity sender, int depth) throws CommandSyntaxException { diff --git a/src/main/java/net/minecraft/network/protocol/game/ServerboundCommandSuggestionPacket.java b/src/main/java/net/minecraft/network/protocol/game/ServerboundCommandSuggestionPacket.java index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/net/minecraft/network/protocol/game/ServerboundCommandSuggestionPacket.java +++ b/src/main/java/net/minecraft/network/protocol/game/ServerboundCommandSuggestionPacket.java @@ -0,0 +0,0 @@ public class ServerboundCommandSuggestionPacket implements Packet 64 && ((index = packet.getCommand().indexOf(' ')) == -1 || index >= 64)) { + this.disconnect(Component.translatable("disconnect.spam"), org.bukkit.event.player.PlayerKickEvent.Cause.SPAM); + return; + } + // Paper end // Paper start - AsyncTabCompleteEvent TAB_COMPLETE_EXECUTOR.execute(() -> this.handleCustomCommandSuggestions0(packet)); } @@ -0,0 +0,0 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl private void sendServerSuggestions(final ServerboundCommandSuggestionPacket packet, final StringReader stringreader) { // Paper end - AsyncTabCompleteEvent ParseResults parseresults = this.server.getCommands().getDispatcher().parse(stringreader, this.player.createCommandSourceStack()); + // Paper start - Handle non-recoverable exceptions + if (!parseresults.getExceptions().isEmpty() + && parseresults.getExceptions().values().stream().anyMatch(e -> e instanceof io.papermc.paper.brigadier.TagParseCommandSyntaxException)) { + this.disconnect(Component.translatable("disconnect.spam"), org.bukkit.event.player.PlayerKickEvent.Cause.SPAM); + return; + } + // Paper end - Handle non-recoverable exceptions this.server.getCommands().getDispatcher().getCompletionSuggestions(parseresults).thenAccept((suggestions) -> { // Paper start - Don't tab-complete namespaced commands if send-namespaced is false