Skip to content

Commit 8b7304d

Browse files
committed
Fix double-eval in EffBroadcast by adding an unformatted hook to VString.getMessageComponents
1 parent dd23f5a commit 8b7304d

3 files changed

Lines changed: 55 additions & 26 deletions

File tree

src/main/java/ch/njol/skript/effects/EffBroadcast.java

Lines changed: 25 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,5 @@
11
package ch.njol.skript.effects;
22

3-
import java.util.ArrayList;
4-
import java.util.Collections;
5-
import java.util.HashSet;
6-
import java.util.List;
7-
import java.util.Set;
8-
import java.util.regex.Pattern;
9-
103
import ch.njol.skript.Skript;
114
import ch.njol.skript.doc.Description;
125
import ch.njol.skript.doc.Examples;
@@ -24,6 +17,7 @@
2417
import ch.njol.skript.util.Utils;
2518
import ch.njol.skript.util.chat.BungeeConverter;
2619
import ch.njol.skript.util.chat.ChatMessages;
20+
import ch.njol.skript.util.chat.MessageComponent;
2721
import ch.njol.util.Kleenean;
2822
import ch.njol.util.StringUtils;
2923
import ch.njol.util.coll.CollectionUtils;
@@ -35,6 +29,9 @@
3529
import org.bukkit.event.server.BroadcastMessageEvent;
3630
import org.jetbrains.annotations.Nullable;
3731

32+
import java.util.*;
33+
import java.util.regex.Pattern;
34+
3835
@Name("Broadcast")
3936
@Description("Broadcasts a message to the server.")
4037
@Examples({
@@ -83,12 +80,15 @@ public void execute(Event event) {
8380
}
8481

8582
for (Expression<?> message : getMessages()) {
86-
if (message instanceof VariableString) {
87-
if (!dispatchEvent(getRawString(event, (VariableString) message), receivers))
83+
if (message instanceof VariableString variableString) {
84+
// get both unformatted and components with single evaluation: https://github.com/SkriptLang/Skript/issues/7718
85+
StringBuilder unformattedString = new StringBuilder();
86+
List<MessageComponent> messageComponents = variableString.getMessageComponents(event, unformattedString);
87+
if (!dispatchEvent(unformattedString.toString(), receivers))
8888
continue;
89-
BaseComponent[] components = BungeeConverter.convert(((VariableString) message).getMessageComponents(event));
89+
BaseComponent[] components = BungeeConverter.convert(messageComponents);
9090
receivers.forEach(receiver -> receiver.spigot().sendMessage(components));
91-
} else if (message instanceof ExprColoured && ((ExprColoured) message).isUnsafeFormat()) { // Manually marked as trusted
91+
} else if (message instanceof ExprColoured coloured && coloured.isUnsafeFormat()) { // Manually marked as trusted
9292
for (Object realMessage : message.getArray(event)) {
9393
if (!dispatchEvent(Utils.replaceChatStyles((String) realMessage), receivers))
9494
continue;
@@ -97,7 +97,7 @@ public void execute(Event event) {
9797
}
9898
} else {
9999
for (Object messageObject : message.getArray(event)) {
100-
String realMessage = messageObject instanceof String ? (String) messageObject : Classes.toString(messageObject);
100+
String realMessage = messageObject instanceof String string ? string : Classes.toString(messageObject);
101101
if (!dispatchEvent(Utils.replaceChatStyles(realMessage), receivers))
102102
continue;
103103
receivers.forEach(receiver -> receiver.sendMessage(realMessage));
@@ -123,9 +123,9 @@ private Expression<?>[] getMessages() {
123123
* @param message the message
124124
* @return true if the dispatched event does not get cancelled
125125
*/
126-
@SuppressWarnings("deprecation")
126+
@SuppressWarnings("removal")
127127
private static boolean dispatchEvent(String message, List<CommandSender> receivers) {
128-
Set<CommandSender> recipients = Collections.unmodifiableSet(new HashSet<>(receivers));
128+
Set<CommandSender> recipients = Set.copyOf(receivers);
129129
BroadcastMessageEvent broadcastEvent;
130130
if (Skript.isRunningMinecraft(1, 14)) {
131131
broadcastEvent = new BroadcastMessageEvent(!Bukkit.isPrimaryThread(), message, recipients);
@@ -136,11 +136,18 @@ private static boolean dispatchEvent(String message, List<CommandSender> receive
136136
return !broadcastEvent.isCancelled();
137137
}
138138

139-
@Nullable
140-
private static String getRawString(Event event, Expression<? extends String> string) {
141-
if (string instanceof VariableString)
142-
return ((VariableString) string).toUnformattedString(event);
139+
/**
140+
* Gets the raw string from the expression, replacing colour codes.
141+
* @param event the event
142+
* @param string the expression
143+
* @return the raw string
144+
*/
145+
private static @Nullable String getRawString(Event event, Expression<? extends String> string) {
146+
if (string instanceof VariableString variableString)
147+
return variableString.toUnformattedString(event);
143148
String rawString = string.getSingle(event);
149+
if (rawString == null)
150+
return null;
144151
rawString = SkriptColor.replaceColorChar(rawString);
145152
if (rawString.toLowerCase().contains("&x")) {
146153
rawString = StringUtils.replaceAll(rawString, HEX_PATTERN, matchResult ->

src/main/java/ch/njol/skript/lang/VariableString.java

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@
2424
import com.google.common.collect.Lists;
2525
import org.bukkit.ChatColor;
2626
import org.bukkit.event.Event;
27-
import org.jetbrains.annotations.Nullable;
2827
import org.jetbrains.annotations.NotNull;
28+
import org.jetbrains.annotations.Nullable;
2929
import org.skriptlang.skript.lang.script.Script;
3030

3131
import java.util.ArrayList;
@@ -385,6 +385,19 @@ public String toUnformattedString(Event event) {
385385
* @return Message components.
386386
*/
387387
public List<MessageComponent> getMessageComponents(Event event) {
388+
return getMessageComponents(event, null);
389+
}
390+
391+
/**
392+
* Gets message components from this string. Formatting is parsed only
393+
* in simple parts for security reasons. Providing a StringBuilder allows an unformatted output
394+
* identical to {@link #toUnformattedString(Event)} while only evaluating any expressions once.
395+
*
396+
* @param event Currently running event.
397+
* @param unformattedBuilder Unformatted string to append to.
398+
* @return Message components.
399+
*/
400+
public List<MessageComponent> getMessageComponents(Event event, @Nullable StringBuilder unformattedBuilder) {
388401
if (isSimple) { // Trusted, constant string in a script
389402
assert simpleUnformatted != null;
390403
return ChatMessages.parse(simpleUnformatted);
@@ -408,14 +421,15 @@ public List<MessageComponent> getMessageComponents(Event event) {
408421

409422
// Convert it to plain text
410423
String text = null;
411-
if (string instanceof ExprColoured && ((ExprColoured) string).isUnsafeFormat()) { // Special case: user wants to process formatting
412-
String unformatted = Classes.toString(((ExprColoured) string).getArray(event), true, mode);
413-
if (unformatted != null) {
414-
message.addAll(ChatMessages.parse(unformatted));
424+
if (string instanceof Expression<?> expression) {
425+
text = Classes.toString(expression.getArray(event), true, mode);
426+
if (unformattedBuilder != null)
427+
unformattedBuilder.append(text);
428+
// Special case: user wants to process formatting
429+
if (string instanceof ExprColoured exprColoured && exprColoured.isUnsafeFormat()) {
430+
message.addAll(ChatMessages.parse(text));
431+
continue;
415432
}
416-
continue;
417-
} else if (string instanceof Expression<?>) {
418-
text = Classes.toString(((Expression<?>) string).getArray(event), true, mode);
419433
}
420434

421435
assert text != null;
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
test "broadcast evaluates vstrings twice":
2+
broadcast "%func()%"
3+
assert {7718::calls} is 1 with "Called func() more than once"
4+
delete {7718::calls}
5+
6+
function func() returns string:
7+
add 1 to {7718::calls}
8+
return "7718 broadcast regression test"

0 commit comments

Comments
 (0)