SDL: core: Factor out common URI decode functions from Wayland and X11

From 71200e94a82b081eb0d426324e3465f7c01f4c09 Mon Sep 17 00:00:00 2001
From: Frank Praznik <[EMAIL REDACTED]>
Date: Wed, 26 Jun 2024 18:13:29 -0400
Subject: [PATCH] core: Factor out common URI decode functions from Wayland and
 X11

---
 src/core/unix/SDL_uri_decode.c        | 123 ++++++++++++++++++++++++
 src/core/unix/SDL_uri_decode.h        |  56 +++++++++++
 src/video/wayland/SDL_waylandevents.c | 129 +-------------------------
 src/video/x11/SDL_x11events.c         | 123 +-----------------------
 4 files changed, 185 insertions(+), 246 deletions(-)
 create mode 100644 src/core/unix/SDL_uri_decode.c
 create mode 100644 src/core/unix/SDL_uri_decode.h

diff --git a/src/core/unix/SDL_uri_decode.c b/src/core/unix/SDL_uri_decode.c
new file mode 100644
index 0000000000000..dd60595864d42
--- /dev/null
+++ b/src/core/unix/SDL_uri_decode.c
@@ -0,0 +1,123 @@
+/*
+  Simple DirectMedia Layer
+  Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
+
+  This software is provided 'as-is', without any express or implied
+  warranty.  In no event will the authors be held liable for any damages
+  arising from the use of this software.
+
+  Permission is granted to anyone to use this software for any purpose,
+  including commercial applications, and to alter it and redistribute it
+  freely, subject to the following restrictions:
+
+  1. The origin of this software must not be misrepresented; you must not
+     claim that you wrote the original software. If you use this software
+     in a product, an acknowledgment in the product documentation would be
+     appreciated but is not required.
+  2. Altered source versions must be plainly marked as such, and must not be
+     misrepresented as being the original software.
+  3. This notice may not be removed or altered from any source distribution.
+*/
+
+#include "SDL_internal.h"
+
+#include "SDL_uri_decode.h"
+#include <errno.h>
+#include <unistd.h>
+
+int SDL_URIDecode(const char *src, char *dst, int len)
+{
+    int ri, wi, di;
+    char decode = '\0';
+    if (!src || !dst || len < 0) {
+        errno = EINVAL;
+        return -1;
+    }
+    if (len == 0) {
+        len = SDL_strlen(src);
+    }
+    for (ri = 0, wi = 0, di = 0; ri < len && wi < len; ri += 1) {
+        if (di == 0) {
+            /* start decoding */
+            if (src[ri] == '%') {
+                decode = '\0';
+                di += 1;
+                continue;
+            }
+            /* normal write */
+            dst[wi] = src[ri];
+            wi += 1;
+            continue;
+        } else if (di == 1 || di == 2) {
+            char off = '\0';
+            char isa = src[ri] >= 'a' && src[ri] <= 'f';
+            char isA = src[ri] >= 'A' && src[ri] <= 'F';
+            char isn = src[ri] >= '0' && src[ri] <= '9';
+            if (!(isa || isA || isn)) {
+                /* not a hexadecimal */
+                int sri;
+                for (sri = ri - di; sri <= ri; sri += 1) {
+                    dst[wi] = src[sri];
+                    wi += 1;
+                }
+                di = 0;
+                continue;
+            }
+            /* itsy bitsy magicsy */
+            if (isn) {
+                off = 0 - '0';
+            } else if (isa) {
+                off = 10 - 'a';
+            } else if (isA) {
+                off = 10 - 'A';
+            }
+            decode |= (src[ri] + off) << (2 - di) * 4;
+            if (di == 2) {
+                dst[wi] = decode;
+                wi += 1;
+                di = 0;
+            } else {
+                di += 1;
+            }
+            continue;
+        }
+    }
+    dst[wi] = '\0';
+    return wi;
+}
+
+int SDL_URIToLocal(const char *src, char *dst)
+{
+    if (SDL_memcmp(src, "file:/", 6) == 0) {
+        src += 6; /* local file? */
+    } else if (SDL_strstr(src, ":/") != NULL) {
+        return -1; /* wrong scheme */
+    }
+
+    SDL_bool local = src[0] != '/' || (src[0] != '\0' && src[1] == '/');
+
+    /* got a hostname? */
+    if (!local && src[0] == '/' && src[2] != '/') {
+        char *hostname_end = SDL_strchr(src + 1, '/');
+        if (hostname_end) {
+            char hostname[257];
+            if (gethostname(hostname, 255) == 0) {
+                hostname[256] = '\0';
+                if (SDL_memcmp(src + 1, hostname, hostname_end - (src + 1)) == 0) {
+                    src = hostname_end + 1;
+                    local = SDL_TRUE;
+                }
+            }
+        }
+    }
+    if (local) {
+        /* Convert URI escape sequences to real characters */
+        if (src[0] == '/') {
+            src++;
+        } else {
+            src--;
+        }
+        return SDL_URIDecode(src, dst, 0);
+    }
+    return -1;
+}
diff --git a/src/core/unix/SDL_uri_decode.h b/src/core/unix/SDL_uri_decode.h
new file mode 100644
index 0000000000000..fbe33f12b8dd9
--- /dev/null
+++ b/src/core/unix/SDL_uri_decode.h
@@ -0,0 +1,56 @@
+/*
+  Simple DirectMedia Layer
+  Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
+
+  This software is provided 'as-is', without any express or implied
+  warranty.  In no event will the authors be held liable for any damages
+  arising from the use of this software.
+
+  Permission is granted to anyone to use this software for any purpose,
+  including commercial applications, and to alter it and redistribute it
+  freely, subject to the following restrictions:
+
+  1. The origin of this software must not be misrepresented; you must not
+     claim that you wrote the original software. If you use this software
+     in a product, an acknowledgment in the product documentation would be
+     appreciated but is not required.
+  2. Altered source versions must be plainly marked as such, and must not be
+     misrepresented as being the original software.
+  3. This notice may not be removed or altered from any source distribution.
+*/
+
+#include "SDL_internal.h"
+
+#ifndef SDL_uri_decode_h_
+#define SDL_uri_decode_h_
+
+/* Decodes URI escape sequences in string src of len bytes
+ * (excluding the terminating NULL byte) into the dst buffer.
+ * Since URI-encoded characters take three times the space of
+ * normal characters, src and dst can safely point to the same
+ * buffer for in situ conversion.
+ *
+ * The buffer is guaranteed to be NULL-terminated, but
+ * may contain embedded NULL bytes.
+ *
+ * Returns the number of decoded bytes that wound up in
+ * the destination buffer, excluding the terminating NULL byte.
+ *
+ * On error, -1 is returned.
+ */
+int SDL_URIDecode(const char *src, char *dst, int len);
+
+/* Convert URI to a local filename, stripping the "file://"
+ * preamble and hostname if present, and writes the result
+ * to the dst buffer. Since URI-encoded characters take
+ * three times the space of normal characters, src and dst
+ * can safely point to the same buffer for in situ conversion.
+ *
+ * Returns the number of decoded bytes that wound up in
+ * the destination buffer, excluding the terminating NULL byte.
+ *
+ * On error, -1 is returned;
+ */
+int SDL_URIToLocal(const char *src, char *dst);
+
+#endif /* SDL_uri_decode_h_ */
diff --git a/src/video/wayland/SDL_waylandevents.c b/src/video/wayland/SDL_waylandevents.c
index 802ba01ab2a9a..693655b399967 100644
--- a/src/video/wayland/SDL_waylandevents.c
+++ b/src/video/wayland/SDL_waylandevents.c
@@ -24,6 +24,7 @@
 #ifdef SDL_VIDEO_DRIVER_WAYLAND
 
 #include "../../core/unix/SDL_poll.h"
+#include "../../core/unix/SDL_uri_decode.h"
 #include "../../events/SDL_events_c.h"
 #include "../../events/SDL_scancode_tables_c.h"
 #include "../../core/linux/SDL_system_theme.h"
@@ -89,8 +90,6 @@ struct SDL_WaylandTouchPoint
 
 static struct wl_list touch_points;
 
-static char *Wayland_URIToLocal(char *uri);
-
 static void touch_add(SDL_TouchID id, wl_fixed_t fx, wl_fixed_t fy, struct wl_surface *surface)
 {
     struct SDL_WaylandTouchPoint *tp = SDL_malloc(sizeof(struct SDL_WaylandTouchPoint));
@@ -2060,127 +2059,6 @@ static void data_device_handle_motion(void *data, struct wl_data_device *wl_data
     }
 }
 
-/* Decodes URI escape sequences in string buf of len bytes
- * (excluding the terminating NULL byte) in-place. Since
- * URI-encoded characters take three times the space of
- * normal characters, this should not be an issue.
- *
- * Returns the number of decoded bytes that wound up in
- * the buffer, excluding the terminating NULL byte.
- *
- * The buffer is guaranteed to be NULL-terminated but
- * may contain embedded NULL bytes.
- *
- * On error, -1 is returned.
- *
- * FIXME: This was shamelessly copied from SDL_x11events.c
- */
-static int Wayland_URIDecode(char *buf, int len)
-{
-    int ri, wi, di;
-    char decode = '\0';
-    if (!buf || len < 0) {
-        errno = EINVAL;
-        return -1;
-    }
-    if (len == 0) {
-        len = SDL_strlen(buf);
-    }
-    for (ri = 0, wi = 0, di = 0; ri < len && wi < len; ri += 1) {
-        if (di == 0) {
-            /* start decoding */
-            if (buf[ri] == '%') {
-                decode = '\0';
-                di += 1;
-                continue;
-            }
-            /* normal write */
-            buf[wi] = buf[ri];
-            wi += 1;
-            continue;
-        } else if (di == 1 || di == 2) {
-            char off = '\0';
-            char isa = buf[ri] >= 'a' && buf[ri] <= 'f';
-            char isA = buf[ri] >= 'A' && buf[ri] <= 'F';
-            char isn = buf[ri] >= '0' && buf[ri] <= '9';
-            if (!(isa || isA || isn)) {
-                /* not a hexadecimal */
-                int sri;
-                for (sri = ri - di; sri <= ri; sri += 1) {
-                    buf[wi] = buf[sri];
-                    wi += 1;
-                }
-                di = 0;
-                continue;
-            }
-            /* itsy bitsy magicsy */
-            if (isn) {
-                off = 0 - '0';
-            } else if (isa) {
-                off = 10 - 'a';
-            } else if (isA) {
-                off = 10 - 'A';
-            }
-            decode |= (buf[ri] + off) << (2 - di) * 4;
-            if (di == 2) {
-                buf[wi] = decode;
-                wi += 1;
-                di = 0;
-            } else {
-                di += 1;
-            }
-            continue;
-        }
-    }
-    buf[wi] = '\0';
-    return wi;
-}
-
-/* Convert URI to local filename
- * return filename if possible, else NULL
- *
- *  FIXME: This was shamelessly copied from SDL_x11events.c
- */
-static char *Wayland_URIToLocal(char *uri)
-{
-    char *file = NULL;
-    SDL_bool local;
-
-    if (SDL_memcmp(uri, "file:/", 6) == 0) {
-        uri += 6; /* local file? */
-    } else if (SDL_strstr(uri, ":/") != NULL) {
-        return file; /* wrong scheme */
-    }
-
-    local = uri[0] != '/' || (uri[0] != '\0' && uri[1] == '/');
-
-    /* got a hostname? */
-    if (!local && uri[0] == '/' && uri[2] != '/') {
-        char *hostname_end = SDL_strchr(uri + 1, '/');
-        if (hostname_end) {
-            char hostname[257];
-            if (gethostname(hostname, 255) == 0) {
-                hostname[256] = '\0';
-                if (SDL_memcmp(uri + 1, hostname, hostname_end - (uri + 1)) == 0) {
-                    uri = hostname_end + 1;
-                    local = SDL_TRUE;
-                }
-            }
-        }
-    }
-    if (local) {
-        file = uri;
-        /* Convert URI escape sequences to real characters */
-        Wayland_URIDecode(file, 0);
-        if (uri[1] == '/') {
-            file++;
-        } else {
-            file--;
-        }
-    }
-    return file;
-}
-
 static void data_device_handle_drop(void *data, struct wl_data_device *wl_data_device)
 {
     SDL_WaylandDataDevice *data_device = data;
@@ -2226,9 +2104,8 @@ static void data_device_handle_drop(void *data, struct wl_data_device *wl_data_d
                 char *saveptr = NULL;
                 char *token = SDL_strtok_r((char *)buffer, "\r\n", &saveptr);
                 while (token) {
-                    char *fn = Wayland_URIToLocal(token);
-                    if (fn) {
-                        SDL_SendDropFile(data_device->dnd_window, NULL, fn);
+                    if (SDL_URIToLocal(token, token) >= 0) {
+                        SDL_SendDropFile(data_device->dnd_window, NULL, token);
                     }
                     token = SDL_strtok_r(NULL, "\r\n", &saveptr);
                 }
diff --git a/src/video/x11/SDL_x11events.c b/src/video/x11/SDL_x11events.c
index decb47a93b787..560d316ca2c7f 100644
--- a/src/video/x11/SDL_x11events.c
+++ b/src/video/x11/SDL_x11events.c
@@ -36,6 +36,7 @@
 #include "SDL_x11settings.h"
 #include "../SDL_clipboard_c.h"
 #include "../../core/unix/SDL_poll.h"
+#include "../../core/unix/SDL_uri_decode.h"
 #include "../../events/SDL_events_c.h"
 #include "../../events/SDL_mouse_c.h"
 #include "../../events/SDL_touch_c.h"
@@ -207,123 +208,6 @@ static SDL_bool X11_IsWheelEvent(Display *display, int button, int *xticks, int
     return SDL_FALSE;
 }
 
-/* Decodes URI escape sequences in string buf of len bytes
-   (excluding the terminating NULL byte) in-place. Since
-   URI-encoded characters take three times the space of
-   normal characters, this should not be an issue.
-
-   Returns the number of decoded bytes that wound up in
-   the buffer, excluding the terminating NULL byte.
-
-   The buffer is guaranteed to be NULL-terminated but
-   may contain embedded NULL bytes.
-
-   On error, -1 is returned.
- */
-static int X11_URIDecode(char *buf, int len)
-{
-    int ri, wi, di;
-    char decode = '\0';
-    if (!buf || len < 0) {
-        errno = EINVAL;
-        return -1;
-    }
-    if (len == 0) {
-        len = SDL_strlen(buf);
-    }
-    for (ri = 0, wi = 0, di = 0; ri < len && wi < len; ri += 1) {
-        if (di == 0) {
-            /* start decoding */
-            if (buf[ri] == '%') {
-                decode = '\0';
-                di += 1;
-                continue;
-            }
-            /* normal write */
-            buf[wi] = buf[ri];
-            wi += 1;
-            continue;
-        } else if (di == 1 || di == 2) {
-            char off = '\0';
-            char isa = buf[ri] >= 'a' && buf[ri] <= 'f';
-            char isA = buf[ri] >= 'A' && buf[ri] <= 'F';
-            char isn = buf[ri] >= '0' && buf[ri] <= '9';
-            if (!(isa || isA || isn)) {
-                /* not a hexadecimal */
-                int sri;
-                for (sri = ri - di; sri <= ri; sri += 1) {
-                    buf[wi] = buf[sri];
-                    wi += 1;
-                }
-                di = 0;
-                continue;
-            }
-            /* itsy bitsy magicsy */
-            if (isn) {
-                off = 0 - '0';
-            } else if (isa) {
-                off = 10 - 'a';
-            } else if (isA) {
-                off = 10 - 'A';
-            }
-            decode |= (buf[ri] + off) << (2 - di) * 4;
-            if (di == 2) {
-                buf[wi] = decode;
-                wi += 1;
-                di = 0;
-            } else {
-                di += 1;
-            }
-            continue;
-        }
-    }
-    buf[wi] = '\0';
-    return wi;
-}
-
-/* Convert URI to local filename
-   return filename if possible, else NULL
-*/
-static char *X11_URIToLocal(char *uri)
-{
-    char *file = NULL;
-    SDL_bool local;
-
-    if (SDL_memcmp(uri, "file:/", 6) == 0) {
-        uri += 6; /* local file? */
-    } else if (SDL_strstr(uri, ":/") != NULL) {
-        return file; /* wrong scheme */
-    }
-
-    local = uri[0] != '/' || (uri[0] != '\0' && uri[1] == '/');
-
-    /* got a hostname? */
-    if (!local && uri[0] == '/' && uri[2] != '/') {
-        char *hostname_end = SDL_strchr(uri + 1, '/');
-        if (hostname_end) {
-            char hostname[257];
-            if (gethostname(hostname, 255) == 0) {
-                hostname[256] = '\0';
-                if (SDL_memcmp(uri + 1, hostname, hostname_end - (uri + 1)) == 0) {
-                    uri = hostname_end + 1;
-                    local = SDL_TRUE;
-                }
-            }
-        }
-    }
-    if (local) {
-        file = uri;
-        /* Convert URI escape sequences to real characters */
-        X11_URIDecode(file, 0);
-        if (uri[1] == '/') {
-            file++;
-        } else {
-            file--;
-        }
-    }
-    return file;
-}
-
 /* An X11 event hook */
 static SDL_X11EventHook g_X11EventHook = NULL;
 static void *g_X11EventHookData = NULL;
@@ -1886,9 +1770,8 @@ static void X11_DispatchEvent(SDL_VideoDevice *_this, XEvent *xevent)
                         if (SDL_strcmp("text/plain", name) == 0) {
                             SDL_SendDropText(data->window, token);
                         } else if (SDL_strcmp("text/uri-list", name) == 0) {
-                            char *fn = X11_URIToLocal(token);
-                            if (fn) {
-                                SDL_SendDropFile(data->window, NULL, fn);
+                            if (SDL_URIToLocal(token, token) >= 0) {
+                                SDL_SendDropFile(data->window, NULL, token);
                             }
                         }
                         token = SDL_strtok_r(NULL, "\r\n", &saveptr);