diff --git a/common/src/main/java/eu/mhsl/craftattack/spawn/common/appliances/security/antiIllegalSignCharacters/SignEditListener.java b/common/src/main/java/eu/mhsl/craftattack/spawn/common/appliances/security/antiIllegalSignCharacters/SignEditListener.java index 9ad38c9..4a7cd94 100644 --- a/common/src/main/java/eu/mhsl/craftattack/spawn/common/appliances/security/antiIllegalSignCharacters/SignEditListener.java +++ b/common/src/main/java/eu/mhsl/craftattack/spawn/common/appliances/security/antiIllegalSignCharacters/SignEditListener.java @@ -6,18 +6,73 @@ import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; import org.bukkit.event.EventHandler; import org.bukkit.event.block.SignChangeEvent; +import java.text.Normalizer; +import java.util.Set; + class SignEditListener extends ApplianceListener { + private static final Set ALLOWED_CHARS = Set.of( + (int)' ', (int)'.', (int)',', (int)';', (int)':', (int)'!', (int)'?', + (int)'"', (int)'\'', + (int)'(', (int)')', (int)'[', (int)']', (int)'{', (int)'}', + (int)'-', (int)'_', (int)'+', (int)'=', (int)'/', (int)'\\', + (int)'@', (int)'#', (int)'$', (int)'%', (int)'&', (int)'*', + (int)'<', (int)'>', (int)'|', + (int)'~', (int)'`', (int)'^' + ); + + private static final Set ALLOWED_EXTRA = Set.of( + (int)'Ä', (int)'Ö', (int)'Ü', (int)'ä', (int)'ö', (int)'ü', (int)'ß', + (int)'€', (int)'°', (int)'µ' + ); + + @EventHandler public void onSignEdit(SignChangeEvent event) { for (int i = 0; i < 4; i++) { Component line = event.line(i); - if(line == null) continue; - String lineStr = PlainTextComponentSerializer.plainText().serialize(line); - - if (!lineStr.matches("^[ -~]*$")) { - String cleaned = lineStr.replaceAll("[^ -~]", ""); - event.line(i, Component.text(cleaned)); - } + if (line == null) continue; + String plainString = PlainTextComponentSerializer.plainText().serialize(line); + plainString = Normalizer.normalize(plainString, Normalizer.Form.NFC); + String cleaned = filterAllowed(plainString); + event.line(i, Component.text(cleaned)); } } + + private static String filterAllowed(String s) { + StringBuilder out = new StringBuilder(s.length()); + + for (int off = 0; off < s.length(); ) { + int cp = s.codePointAt(off); + off += Character.charCount(cp); + + if (isForbidden(cp)) continue; + + if (Character.isLetterOrDigit(cp)) { + out.appendCodePoint(cp); + continue; + } + + if (ALLOWED_CHARS.contains(cp) || ALLOWED_EXTRA.contains(cp)) { + out.appendCodePoint(cp); + } + } + return out.toString(); + } + + private static boolean isForbidden(int cp) { + // Surrogates / invalid + if (cp >= 0xD800 && cp <= 0xDFFF) return true; + + // Private Use Area (Mod/Pack-Icons/Placeholder) + if (cp >= 0xE000 && cp <= 0xF8FF) return true; + + // Zero-width and control characters + if (cp == 0x200B || cp == 0x200C || cp == 0x200D || cp == 0xFEFF) return true; + + // BiDi-Steuerzeichen + if (cp >= 0x202A && cp <= 0x202E) return true; + if (cp >= 0x2066 && cp <= 0x2069) return true; + + return cp == '§'; + } }