From 3d80355d9081e4304be454ecb12828b21dcabb2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Efe=20G=C3=B6kdemir?= Date: Thu, 24 Sep 2026 08:29:36 +0300 Subject: [PATCH] Fix UTF-16 surrogate validation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Efe Gökdemir --- .../main/java/org/apache/bifromq/util/UTF8Util.java | 11 +++++++++-- .../java/org/apache/bifromq/util/UTF8UtilTest.java | 5 ++++- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/bifromq-util/src/main/java/org/apache/bifromq/util/UTF8Util.java b/bifromq-util/src/main/java/org/apache/bifromq/util/UTF8Util.java index c84edda5b..603f19480 100644 --- a/bifromq-util/src/main/java/org/apache/bifromq/util/UTF8Util.java +++ b/bifromq-util/src/main/java/org/apache/bifromq/util/UTF8Util.java @@ -39,6 +39,9 @@ public static boolean isWellFormed(String str, boolean sanityCheck) { if (cl == '\u0000') { return false; } + if (Character.isLowSurrogate(cl)) { + return false; + } if (sanityCheck && isUnacceptableChar(cl)) { return false; } @@ -47,7 +50,11 @@ public static boolean isWellFormed(String str, boolean sanityCheck) { if (cr == '\u0000') { return false; } - if (Character.isSurrogatePair(cl, cr)) { + if (Character.isHighSurrogate(cl)) { + if (!Character.isLowSurrogate(cr)) { + return false; + } + } else if (Character.isLowSurrogate(cr)) { return false; } if (sanityCheck && isUnacceptableChar(cr)) { @@ -55,7 +62,7 @@ public static boolean isWellFormed(String str, boolean sanityCheck) { } cl = cr; } - return true; + return !Character.isHighSurrogate(cl); } public static boolean isValidUTF8Payload(ByteBuffer payload) { diff --git a/bifromq-util/src/test/java/org/apache/bifromq/util/UTF8UtilTest.java b/bifromq-util/src/test/java/org/apache/bifromq/util/UTF8UtilTest.java index e9f7000ac..0448126d6 100644 --- a/bifromq-util/src/test/java/org/apache/bifromq/util/UTF8UtilTest.java +++ b/bifromq-util/src/test/java/org/apache/bifromq/util/UTF8UtilTest.java @@ -47,7 +47,10 @@ public void emptyClientId() { @Test public void mustNotChars() { assertFalse(UTF8Util.isWellFormed("hello\u0000world", false)); // null character U+0000 - assertFalse(UTF8Util.isWellFormed("hello\uD83D\uDE0Aworld", false)); // surrogate pairs + assertTrue(UTF8Util.isWellFormed("hello\uD83D\uDE0Aworld", false)); // valid surrogate pair + assertFalse(UTF8Util.isWellFormed("hello\uD83Dworld", false)); // unpaired high surrogate + assertFalse(UTF8Util.isWellFormed("hello\uDE0Aworld", false)); // unpaired low surrogate + assertFalse(UTF8Util.isWellFormed("hello\uD83D", false)); // trailing high surrogate } @Test