From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Nassim Jahnke <nassim@njahnke.dev>
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<S> {
             }
             final CommandContextBuilder<S> 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<S> {
                 }
                 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/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<CompoundTag> 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/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 <T> Optional<T> visit(FormattedText.ContentConsumer<T> visitor) {
+        // Paper start - Count visited parts
+        try {
+            return this.visit(new TranslatableContentConsumer<>(visitor));
+        } catch (IllegalArgumentException ignored) {
+            return visitor.accept("...");
+        }
+    }
+    private <T> Optional<T> visit(TranslatableContentConsumer<T> 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<T> implements FormattedText.ContentConsumer<T> {
+        private static final IllegalArgumentException EX = new IllegalArgumentException("Too long");
+        private final FormattedText.ContentConsumer<T> visitor;
+        private int visited;
+
+        private TranslatableContentConsumer(FormattedText.ContentConsumer<T> visitor) {
+            this.visitor = visitor;
+        }
+
+        @Override
+        public Optional<T> 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<ServerGamePack
 
     private ServerboundCommandSuggestionPacket(FriendlyByteBuf buf) {
         this.id = buf.readVarInt();
-        this.command = buf.readUtf(32500);
+        this.command = buf.readUtf(2048); // Paper
     }
 
     private void write(FriendlyByteBuf buf) {
diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
@@ -0,0 +0,0 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl
             return;
         }
         // Paper end - Don't suggest if tab-complete is disabled
+        // Paper start
+        final int index;
+        if (packet.getCommand().length() > 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<CommandSourceStack> 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