SDL: Only convert the result of XLookupString() if it's not already UTF-8

From 491ae20d963cff397b5980b31d142d576e74becb Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Sun, 4 Jun 2023 02:06:52 -0700
Subject: [PATCH] Only convert the result of XLookupString() if it's not
 already UTF-8

Fixes https://github.com/libsdl-org/SDL/issues/7766
---
 src/SDL_utils_c.h             |  5 ++++-
 src/stdlib/SDL_string.c       | 38 ++++++++++++++++++++++++++++++++++-
 src/video/x11/SDL_x11events.c |  3 ++-
 3 files changed, 43 insertions(+), 3 deletions(-)

diff --git a/src/SDL_utils_c.h b/src/SDL_utils_c.h
index 30061016b400..829f050de552 100644
--- a/src/SDL_utils_c.h
+++ b/src/SDL_utils_c.h
@@ -26,6 +26,9 @@
 /* Common utility functions that aren't in the public API */
 
 /* Return the smallest power of 2 greater than or equal to 'x' */
-int SDL_powerof2(int x);
+extern int SDL_powerof2(int x);
+
+/* Return whether the string is valid UTF8 */
+extern SDL_bool SDL_utf8valid(const char *str, size_t bytes);
 
 #endif /* SDL_utils_h_ */
diff --git a/src/stdlib/SDL_string.c b/src/stdlib/SDL_string.c
index 4d72c28f8134..9cc74b2d52d0 100644
--- a/src/stdlib/SDL_string.c
+++ b/src/stdlib/SDL_string.c
@@ -36,7 +36,7 @@
 #define UTF8_IsLeadByte(c)     ((c) >= 0xC0 && (c) <= 0xF4)
 #define UTF8_IsTrailingByte(c) ((c) >= 0x80 && (c) <= 0xBF)
 
-static unsigned UTF8_GetTrailingBytes(unsigned char c)
+static size_t UTF8_GetTrailingBytes(unsigned char c)
 {
     if (c >= 0xC0 && c <= 0xDF) {
         return 1;
@@ -676,6 +676,42 @@ size_t SDL_utf8strnlen(const char *str, size_t bytes)
     return retval;
 }
 
+SDL_bool SDL_utf8valid(const char *str, size_t bytes)
+{
+    while (*str && bytes > 0) {
+        Uint8 ch = (Uint8)*str;
+
+        if (ch <= 0x7F) {
+            ++str;
+            --bytes;
+            continue;
+        }
+
+        if (UTF8_IsLeadByte(ch)) {
+            size_t left = UTF8_GetTrailingBytes(ch);
+            if (bytes < left) {
+                return SDL_FALSE;
+            }
+
+            ++str;
+            --bytes;
+
+            while (left-- > 0) {
+                ch = (Uint8)*str;
+                if (!UTF8_IsTrailingByte(ch)) {
+                    return SDL_FALSE;
+                }
+
+                ++str;
+                --bytes;
+            }
+        } else {
+            return SDL_FALSE;
+        }
+    }
+    return SDL_TRUE;
+}
+
 size_t SDL_strlcat(SDL_INOUT_Z_CAP(maxlen) char *dst, const char *src, size_t maxlen)
 {
 #ifdef HAVE_STRLCAT
diff --git a/src/video/x11/SDL_x11events.c b/src/video/x11/SDL_x11events.c
index cc2f8564efa7..b37d8acad5be 100644
--- a/src/video/x11/SDL_x11events.c
+++ b/src/video/x11/SDL_x11events.c
@@ -37,6 +37,7 @@
 #include "../../events/SDL_mouse_c.h"
 #include "../../events/SDL_touch_c.h"
 #include "../../core/linux/SDL_system_theme.h"
+#include "../../SDL_utils_c.h"
 
 #include <SDL3/SDL_syswm.h>
 
@@ -762,7 +763,7 @@ static Bool isReparentNotify(Display *display, XEvent *ev, XPointer arg)
 static int XLookupStringAsUTF8(XKeyEvent *event_struct, char *buffer_return, int bytes_buffer, KeySym *keysym_return, XComposeStatus *status_in_out)
 {
     int result = X11_XLookupString(event_struct, buffer_return, bytes_buffer, keysym_return, status_in_out);
-    if (result > 0) {
+    if (result > 0 && !SDL_utf8valid(buffer_return, (size_t)result)) {
         char *utf8_text = SDL_iconv_string("UTF-8", "ISO-8859-1", buffer_return, result);
         if (utf8_text) {
             SDL_strlcpy(buffer_return, utf8_text, bytes_buffer);