aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorJake Potrebic <[email protected]>2024-05-12 13:46:46 -0700
committerGitHub <[email protected]>2024-05-12 13:46:46 -0700
commit11c39637de9eb0b14aee8d3b4a4bdc9799b7d13d (patch)
tree98f48f3fb4919a543dac6f96f2b2a3e86634c12e
parentf041f48458e6d81a54895e988867c0b738922d4d (diff)
downloadPaper-11c39637de9eb0b14aee8d3b4a4bdc9799b7d13d.tar.gz
Paper-11c39637de9eb0b14aee8d3b4a4bdc9799b7d13d.zip
Improve Recipe validation (#10707)
-rw-r--r--patches/api/0479-Improve-Recipe-validation.patch252
1 files changed, 252 insertions, 0 deletions
diff --git a/patches/api/0479-Improve-Recipe-validation.patch b/patches/api/0479-Improve-Recipe-validation.patch
new file mode 100644
index 0000000000..47d68fde83
--- /dev/null
+++ b/patches/api/0479-Improve-Recipe-validation.patch
@@ -0,0 +1,252 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Jake Potrebic <[email protected]>
+Date: Sun, 12 May 2024 10:42:42 -0700
+Subject: [PATCH] Improve Recipe validation
+
+
+diff --git a/src/main/java/org/bukkit/inventory/CookingRecipe.java b/src/main/java/org/bukkit/inventory/CookingRecipe.java
+index f7fa79393aef40027446b78bac8e9490cfafd8bc..a64dae15db89038f3eb599f2a41edf45990f4fda 100644
+--- a/src/main/java/org/bukkit/inventory/CookingRecipe.java
++++ b/src/main/java/org/bukkit/inventory/CookingRecipe.java
+@@ -44,10 +44,10 @@ public abstract class CookingRecipe<T extends CookingRecipe> implements Recipe,
+ * @param cookingTime The cooking time (in ticks)
+ */
+ public CookingRecipe(@NotNull NamespacedKey key, @NotNull ItemStack result, @NotNull RecipeChoice input, float experience, int cookingTime) {
+- Preconditions.checkArgument(result.getType() != Material.AIR, "Recipe must have non-AIR result.");
++ Preconditions.checkArgument(!result.isEmpty(), "Recipe cannot have an empty result."); // Paper
+ this.key = key;
+ this.output = new ItemStack(result);
+- this.ingredient = input;
++ this.ingredient = input.validate().clone(); // Paper
+ this.experience = experience;
+ this.cookingTime = cookingTime;
+ }
+@@ -84,7 +84,7 @@ public abstract class CookingRecipe<T extends CookingRecipe> implements Recipe,
+ */
+ @NotNull
+ public T setInputChoice(@NotNull RecipeChoice input) {
+- this.ingredient = input;
++ this.ingredient = input.validate().clone(); // Paper
+ return (T) this;
+ }
+
+diff --git a/src/main/java/org/bukkit/inventory/CraftingRecipe.java b/src/main/java/org/bukkit/inventory/CraftingRecipe.java
+index e4bf772f7e06f38215bee68f089b15a4fcb12817..37024b4736dd3897490ca51d08cf07901b01d59f 100644
+--- a/src/main/java/org/bukkit/inventory/CraftingRecipe.java
++++ b/src/main/java/org/bukkit/inventory/CraftingRecipe.java
+@@ -18,7 +18,7 @@ public abstract class CraftingRecipe implements Recipe, Keyed {
+
+ protected CraftingRecipe(@NotNull NamespacedKey key, @NotNull ItemStack result) {
+ Preconditions.checkArgument(key != null, "key cannot be null");
+- Preconditions.checkArgument(result.getType() != Material.AIR, "Recipe must have non-AIR result.");
++ Preconditions.checkArgument(!result.isEmpty(), "Recipe cannot have an empty result."); // Paper
+ this.key = key;
+ this.output = new ItemStack(result);
+ }
+diff --git a/src/main/java/org/bukkit/inventory/MerchantRecipe.java b/src/main/java/org/bukkit/inventory/MerchantRecipe.java
+index 39f9766a03d420340d79841197f75c8b1dd49f4a..4e59f5176fd6cf92457ad750081c253a58790b61 100644
+--- a/src/main/java/org/bukkit/inventory/MerchantRecipe.java
++++ b/src/main/java/org/bukkit/inventory/MerchantRecipe.java
+@@ -79,6 +79,7 @@ public class MerchantRecipe implements Recipe {
+ this(result, uses, maxUses, experienceReward, villagerExperience, priceMultiplier, 0, 0, ignoreDiscounts);
+ }
+ public MerchantRecipe(@NotNull ItemStack result, int uses, int maxUses, boolean experienceReward, int villagerExperience, float priceMultiplier, int demand, int specialPrice, boolean ignoreDiscounts) {
++ Preconditions.checkArgument(!result.isEmpty(), "Recipe cannot have an empty result."); // Paper
+ this.ignoreDiscounts = ignoreDiscounts;
+ // Paper end
+ this.result = result;
+@@ -101,11 +102,12 @@ public class MerchantRecipe implements Recipe {
+ @NotNull
+ @Override
+ public ItemStack getResult() {
+- return result;
++ return result.clone(); // Paper
+ }
+
+ public void addIngredient(@NotNull ItemStack item) {
+ Preconditions.checkState(ingredients.size() < 2, "MerchantRecipe can only have maximum 2 ingredients");
++ Preconditions.checkArgument(!item.isEmpty(), "Recipe cannot have an empty itemstack ingredient."); // Paper
+ ingredients.add(item.clone());
+ }
+
+@@ -117,6 +119,7 @@ public class MerchantRecipe implements Recipe {
+ Preconditions.checkState(ingredients.size() <= 2, "MerchantRecipe can only have maximum 2 ingredients");
+ this.ingredients = new ArrayList<ItemStack>();
+ for (ItemStack item : ingredients) {
++ Preconditions.checkArgument(!item.isEmpty(), "Recipe cannot have an empty itemstack ingredient."); // Paper
+ this.ingredients.add(item.clone());
+ }
+ }
+diff --git a/src/main/java/org/bukkit/inventory/RecipeChoice.java b/src/main/java/org/bukkit/inventory/RecipeChoice.java
+index db8bcc66bdc4bedfffb4705db6338eda4c0ad29a..c143b0d8b97d514566bac8413d0346cf50822aeb 100644
+--- a/src/main/java/org/bukkit/inventory/RecipeChoice.java
++++ b/src/main/java/org/bukkit/inventory/RecipeChoice.java
+@@ -35,6 +35,13 @@ public interface RecipeChoice extends Predicate<ItemStack>, Cloneable {
+ @Override
+ boolean test(@NotNull ItemStack itemStack);
+
++ // Paper start - check valid ingredients
++ @org.jetbrains.annotations.ApiStatus.Internal
++ default @NotNull RecipeChoice validate() {
++ return this;
++ }
++ // Paper end - check valid ingredients
++
+ /**
+ * Represents a choice of multiple matching Materials.
+ */
+@@ -185,7 +192,12 @@ public interface RecipeChoice extends Predicate<ItemStack>, Cloneable {
+ public ExactChoice clone() {
+ try {
+ ExactChoice clone = (ExactChoice) super.clone();
+- clone.choices = new ArrayList<>(choices);
++ // Paper start - properly clone
++ clone.choices = new ArrayList<>(this.choices.size());
++ for (ItemStack choice : this.choices) {
++ clone.choices.add(choice.clone());
++ }
++ // Paper end - properly clone
+ return clone;
+ } catch (CloneNotSupportedException ex) {
+ throw new AssertionError(ex);
+@@ -232,5 +244,15 @@ public interface RecipeChoice extends Predicate<ItemStack>, Cloneable {
+ public String toString() {
+ return "ExactChoice{" + "choices=" + choices + '}';
+ }
++
++ // Paper start - check valid ingredients
++ @Override
++ public @NotNull RecipeChoice validate() {
++ if (this.choices.stream().anyMatch(s -> s.getType().isAir())) {
++ throw new IllegalArgumentException("RecipeChoice.ExactChoice cannot contain air");
++ }
++ return this;
++ }
++ // Paper end - check valid ingredients
+ }
+ }
+diff --git a/src/main/java/org/bukkit/inventory/ShapedRecipe.java b/src/main/java/org/bukkit/inventory/ShapedRecipe.java
+index da878c6d4928ddbc16b50ace86d992685a2b7873..0547505987ee954adbac0cbe5a6ada3dfaedf034 100644
+--- a/src/main/java/org/bukkit/inventory/ShapedRecipe.java
++++ b/src/main/java/org/bukkit/inventory/ShapedRecipe.java
+@@ -166,14 +166,15 @@ public class ShapedRecipe extends CraftingRecipe {
+ public ShapedRecipe setIngredient(char key, @NotNull RecipeChoice ingredient) {
+ Preconditions.checkArgument(ingredients.containsKey(key), "Symbol does not appear in the shape:", key);
+
+- ingredients.put(key, ingredient);
++ ingredients.put(key, ingredient.validate().clone()); // Paper
+ return this;
+ }
+
+ // Paper start
+ @NotNull
+ public ShapedRecipe setIngredient(char key, @NotNull ItemStack item) {
+- return setIngredient(key, new RecipeChoice.ExactChoice(item));
++ Preconditions.checkArgument(!item.getType().isAir(), "Item cannot be air"); // Paper
++ return setIngredient(key, new RecipeChoice.ExactChoice(item.clone())); // Paper
+ }
+ // Paper end
+
+diff --git a/src/main/java/org/bukkit/inventory/ShapelessRecipe.java b/src/main/java/org/bukkit/inventory/ShapelessRecipe.java
+index beb798482479c58a8628c314b510ab6349576ce8..6a8195ca224790e3130ec9923188e7b585797263 100644
+--- a/src/main/java/org/bukkit/inventory/ShapelessRecipe.java
++++ b/src/main/java/org/bukkit/inventory/ShapelessRecipe.java
+@@ -131,7 +131,7 @@ public class ShapelessRecipe extends CraftingRecipe {
+ public ShapelessRecipe addIngredient(@NotNull RecipeChoice ingredient) {
+ Preconditions.checkArgument(ingredients.size() + 1 <= 9, "Shapeless recipes cannot have more than 9 ingredients");
+
+- ingredients.add(ingredient);
++ ingredients.add(ingredient.validate().clone()); // Paper
+ return this;
+ }
+
+@@ -144,6 +144,8 @@ public class ShapelessRecipe extends CraftingRecipe {
+ @NotNull
+ public ShapelessRecipe addIngredient(int count, @NotNull ItemStack item) {
+ Preconditions.checkArgument(ingredients.size() + count <= 9, "Shapeless recipes cannot have more than 9 ingredients");
++ Preconditions.checkArgument(!item.getType().isAir(), "Item cannot be air"); // Paper
++ item = item.clone(); // Paper
+ while (count-- > 0) {
+ ingredients.add(new RecipeChoice.ExactChoice(item));
+ }
+diff --git a/src/main/java/org/bukkit/inventory/SmithingRecipe.java b/src/main/java/org/bukkit/inventory/SmithingRecipe.java
+index 1ef9a715a2736e88a16083c6873803a8bd6bcf29..99e40588c10141391762f37b5a326f8df06e5276 100644
+--- a/src/main/java/org/bukkit/inventory/SmithingRecipe.java
++++ b/src/main/java/org/bukkit/inventory/SmithingRecipe.java
+@@ -44,12 +44,13 @@ public class SmithingRecipe implements Recipe, Keyed {
+ */
+ @Deprecated
+ public SmithingRecipe(@NotNull NamespacedKey key, @NotNull ItemStack result, @NotNull RecipeChoice base, @NotNull RecipeChoice addition, boolean copyDataComponents) {
++ com.google.common.base.Preconditions.checkArgument(!result.isEmpty(), "Recipe cannot have an empty result."); // Paper
+ this.copyDataComponents = copyDataComponents;
+ // Paper end
+ this.key = key;
+ this.result = result;
+- this.base = base;
+- this.addition = addition;
++ this.base = base.validate().clone(); // Paper
++ this.addition = addition.validate().clone(); // Paper
+ }
+
+ /**
+diff --git a/src/main/java/org/bukkit/inventory/SmithingTransformRecipe.java b/src/main/java/org/bukkit/inventory/SmithingTransformRecipe.java
+index 68e7132d77151b7b8312638d8bb79ea59e2fa5a6..d3a7070a51d15531fb6f917ca87196dfa08f83aa 100644
+--- a/src/main/java/org/bukkit/inventory/SmithingTransformRecipe.java
++++ b/src/main/java/org/bukkit/inventory/SmithingTransformRecipe.java
+@@ -21,7 +21,7 @@ public class SmithingTransformRecipe extends SmithingRecipe {
+ */
+ public SmithingTransformRecipe(@NotNull NamespacedKey key, @NotNull ItemStack result, @NotNull RecipeChoice template, @NotNull RecipeChoice base, @NotNull RecipeChoice addition) {
+ super(key, result, base, addition);
+- this.template = template;
++ this.template = template.validate().clone(); // Paper
+ }
+ // Paper start
+ /**
+diff --git a/src/main/java/org/bukkit/inventory/SmithingTrimRecipe.java b/src/main/java/org/bukkit/inventory/SmithingTrimRecipe.java
+index ce36bb5b030f17e11f74e987235be143c1925aa7..6316112074a0708734106ca9de5ef968df52ce0e 100644
+--- a/src/main/java/org/bukkit/inventory/SmithingTrimRecipe.java
++++ b/src/main/java/org/bukkit/inventory/SmithingTrimRecipe.java
+@@ -21,7 +21,7 @@ public class SmithingTrimRecipe extends SmithingRecipe implements ComplexRecipe
+ */
+ public SmithingTrimRecipe(@NotNull NamespacedKey key, @NotNull RecipeChoice template, @NotNull RecipeChoice base, @NotNull RecipeChoice addition) {
+ super(key, new ItemStack(Material.AIR), base, addition);
+- this.template = template;
++ this.template = template.validate().clone(); // Paper
+ }
+ // Paper start
+ /**
+@@ -35,7 +35,7 @@ public class SmithingTrimRecipe extends SmithingRecipe implements ComplexRecipe
+ */
+ public SmithingTrimRecipe(@NotNull NamespacedKey key, @NotNull RecipeChoice template, @NotNull RecipeChoice base, @NotNull RecipeChoice addition, boolean copyDataComponents) {
+ super(key, new ItemStack(Material.AIR), base, addition, copyDataComponents);
+- this.template = template;
++ this.template = template.validate().clone(); // Paper
+ }
+ // Paper end
+
+diff --git a/src/main/java/org/bukkit/inventory/StonecuttingRecipe.java b/src/main/java/org/bukkit/inventory/StonecuttingRecipe.java
+index bc3440eb72127824b3961fbdae583bb61385f65e..802c23a86d301a5336a97a8256182da7d792ec1d 100644
+--- a/src/main/java/org/bukkit/inventory/StonecuttingRecipe.java
++++ b/src/main/java/org/bukkit/inventory/StonecuttingRecipe.java
+@@ -35,10 +35,10 @@ public class StonecuttingRecipe implements Recipe, Keyed {
+ * @param input The input choices.
+ */
+ public StonecuttingRecipe(@NotNull NamespacedKey key, @NotNull ItemStack result, @NotNull RecipeChoice input) {
+- Preconditions.checkArgument(result.getType() != Material.AIR, "Recipe must have non-AIR result.");
++ Preconditions.checkArgument(!result.isEmpty(), "Recipe cannot have an empty result."); // Paper
+ this.key = key;
+ this.output = new ItemStack(result);
+- this.ingredient = input;
++ this.ingredient = input.validate().clone(); // Paper
+ }
+
+ /**
+@@ -73,7 +73,7 @@ public class StonecuttingRecipe implements Recipe, Keyed {
+ */
+ @NotNull
+ public StonecuttingRecipe setInputChoice(@NotNull RecipeChoice input) {
+- this.ingredient = input;
++ this.ingredient = input.validate().clone(); // Paper
+ return (StonecuttingRecipe) this;
+ }
+