Skip to content

Commit 411d58f

Browse files
committed
Removed hex colors from webhooks
Adds sanitization and JSON escaping for webhook content and embed descriptions to remove legacy formatting codes and ensure safe payloads. This prevents formatting issues and potential injection by cleaning input before sending to the webhook endpoint.
1 parent 1d149bd commit 411d58f

File tree

1 file changed

+56
-3
lines changed

1 file changed

+56
-3
lines changed

src/main/java/ppn/Webhook.java

Lines changed: 56 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,27 @@
11
package ppn;
22

3+
import net.kyori.adventure.text.Component;
4+
import net.kyori.adventure.text.minimessage.MiniMessage;
5+
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
6+
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
7+
38
import java.io.OutputStream;
49
import java.net.HttpURLConnection;
510
import java.net.URL;
611
import java.nio.charset.StandardCharsets;
12+
import java.util.regex.Pattern;
713

814
public class Webhook {
15+
16+
private static final MiniMessage MINI_MESSAGE = MiniMessage.miniMessage();
17+
private static final PlainTextComponentSerializer PLAIN_SERIALIZER = PlainTextComponentSerializer.plainText();
18+
private static final LegacyComponentSerializer AMPERSAND_SERIALIZER = LegacyComponentSerializer.legacyAmpersand();
19+
private static final LegacyComponentSerializer SECTION_SERIALIZER = LegacyComponentSerializer.legacySection();
20+
private static final Pattern LEGACY_CODE_PATTERN = Pattern.compile("(?i)[&§][0-9A-FK-ORX]");
21+
private static final Pattern RGB_HEX_PATTERN = Pattern.compile("(?i)&#[0-9A-F]{6}");
22+
private static final Pattern SHORT_RGB_HEX_PATTERN = Pattern.compile("(?i)&#[0-9A-F]{3}");
23+
private static final Pattern BUKKIT_HEX_PATTERN = Pattern.compile("(?i)&x(?:&[0-9A-F]){6}");
24+
925
public static void send(String url, String content) {
1026
if (url == null || url.isEmpty()) {
1127
return;
@@ -15,7 +31,8 @@ public static void send(String url, String content) {
1531
connection.setRequestMethod("POST");
1632
connection.setDoOutput(true);
1733
connection.setRequestProperty("Content-Type", "application/json");
18-
String payload = "{\"content\":\"" + content.replace("\"", "\\\"") + "\"}";
34+
String sanitized = sanitize(content);
35+
String payload = "{\"content\":\"" + escapeJson(sanitized) + "\"}";
1936
try (OutputStream os = connection.getOutputStream()) {
2037
os.write(payload.getBytes(StandardCharsets.UTF_8));
2138
}
@@ -33,13 +50,49 @@ public static void sendEmbed(String url, String description, int color) {
3350
connection.setRequestMethod("POST");
3451
connection.setDoOutput(true);
3552
connection.setRequestProperty("Content-Type", "application/json");
36-
String safe = description == null ? "" : description.replace("\"", "\\\"");
37-
String payload = "{\"embeds\":[{\"description\":\"" + safe + "\",\"color\":" + color + "}]}";
53+
String sanitized = sanitize(description);
54+
String payload = "{\"embeds\":[{\"description\":\"" + escapeJson(sanitized) + "\",\"color\":" + color + "}]}";
3855
try (OutputStream os = connection.getOutputStream()) {
3956
os.write(payload.getBytes(StandardCharsets.UTF_8));
4057
}
4158
connection.getInputStream().close();
4259
} catch (Exception ignored) {
4360
}
4461
}
62+
63+
private static String sanitize(String input) {
64+
if (input == null || input.isEmpty()) {
65+
return "";
66+
}
67+
String sanitized = input;
68+
69+
try {
70+
Component component = MINI_MESSAGE.deserialize(input);
71+
sanitized = PLAIN_SERIALIZER.serialize(component);
72+
} catch (Exception ignored) {
73+
}
74+
75+
if (LEGACY_CODE_PATTERN.matcher(input).find()) {
76+
Component legacyComponent = AMPERSAND_SERIALIZER.deserialize(input);
77+
sanitized = PLAIN_SERIALIZER.serialize(legacyComponent);
78+
} else if (input.indexOf('§') >= 0) {
79+
Component legacyComponent = SECTION_SERIALIZER.deserialize(input);
80+
sanitized = PLAIN_SERIALIZER.serialize(legacyComponent);
81+
}
82+
83+
sanitized = RGB_HEX_PATTERN.matcher(sanitized).replaceAll("");
84+
sanitized = SHORT_RGB_HEX_PATTERN.matcher(sanitized).replaceAll("");
85+
sanitized = BUKKIT_HEX_PATTERN.matcher(sanitized).replaceAll("");
86+
sanitized = LEGACY_CODE_PATTERN.matcher(sanitized).replaceAll("");
87+
return sanitized;
88+
}
89+
90+
private static String escapeJson(String input) {
91+
if (input == null || input.isEmpty()) {
92+
return "";
93+
}
94+
String escaped = input.replace("\\", "\\\\").replace("\"", "\\\"");
95+
escaped = escaped.replace("\n", "\\n").replace("\r", "\\r").replace("\t", "\\t");
96+
return escaped;
97+
}
4598
}

0 commit comments

Comments
 (0)