SDL: filesystem: SDL_EnumerateDirectory() gives dirs with path seperators appended.

From 87e1b0eb89823156551c6244ed365f063a3b1dcf Mon Sep 17 00:00:00 2001
From: "Ryan C. Gordon" <[EMAIL REDACTED]>
Date: Wed, 15 Jan 2025 17:03:01 -0500
Subject: [PATCH] filesystem: SDL_EnumerateDirectory() gives dirs with path
 seperators appended.

Fixes #11065.
Fixes #11427.
---
 include/SDL3/SDL_filesystem.h         |  3 +++
 src/filesystem/SDL_filesystem.c       |  5 ++--
 src/filesystem/posix/SDL_sysfsops.c   | 27 ++++++++++++++++++----
 src/filesystem/windows/SDL_sysfsops.c | 33 ++++++++++++++++++---------
 test/testfilesystem.c                 |  9 +-------
 5 files changed, 51 insertions(+), 26 deletions(-)

diff --git a/include/SDL3/SDL_filesystem.h b/include/SDL3/SDL_filesystem.h
index c228a0382f4bb..8baa254441952 100644
--- a/include/SDL3/SDL_filesystem.h
+++ b/include/SDL3/SDL_filesystem.h
@@ -313,6 +313,9 @@ typedef enum SDL_EnumerationResult
  * terminate the enumeration early, and dictate the return value of the
  * enumeration function itself.
  *
+ * `dirname` is guaranteed to end with a path separator ('\\' on
+ * Windows, '/' on most other platforms).
+ *
  * \param userdata an app-controlled pointer that is passed to the callback.
  * \param dirname the directory that is being enumerated.
  * \param fname the next entry in the enumeration.
diff --git a/src/filesystem/SDL_filesystem.c b/src/filesystem/SDL_filesystem.c
index 6bb905a0b851e..28388517c4978 100644
--- a/src/filesystem/SDL_filesystem.c
+++ b/src/filesystem/SDL_filesystem.c
@@ -307,7 +307,7 @@ static SDL_EnumerationResult SDLCALL GlobDirectoryCallback(void *userdata, const
     // !!! FIXME: and only casefold the new pieces instead of allocating and folding full paths for all of this.
 
     char *fullpath = NULL;
-    if (SDL_asprintf(&fullpath, "%s/%s", dirname, fname) < 0) {
+    if (SDL_asprintf(&fullpath, "%s%s", dirname, fname) < 0) {
         return SDL_ENUM_FAILURE;
     }
 
@@ -417,7 +417,8 @@ char **SDL_InternalGlobDirectory(const char *path, const char *pattern, SDL_Glob
     data.enumerator = enumerator;
     data.getpathinfo = getpathinfo;
     data.fsuserdata = userdata;
-    data.basedirlen = SDL_strlen(path) + 1;  // +1 for the '/' we'll be adding.
+    data.basedirlen = *path ? (SDL_strlen(path) + 1) : 0;  // +1 for the '/' we'll be adding.
+
 
     char **result = NULL;
     if (data.enumerator(path, GlobDirectoryCallback, &data, data.fsuserdata)) {
diff --git a/src/filesystem/posix/SDL_sysfsops.c b/src/filesystem/posix/SDL_sysfsops.c
index 0658b8f1554d9..c562c2aed2461 100644
--- a/src/filesystem/posix/SDL_sysfsops.c
+++ b/src/filesystem/posix/SDL_sysfsops.c
@@ -37,25 +37,42 @@
 
 bool SDL_SYS_EnumerateDirectory(const char *path, SDL_EnumerateDirectoryCallback cb, void *userdata)
 {
-    SDL_EnumerationResult result = SDL_ENUM_CONTINUE;
+    char *pathwithsep = NULL;
+    int pathwithseplen = SDL_asprintf(&pathwithsep, "%s/", path);
+    if ((pathwithseplen == -1) || (!pathwithsep)) {
+        return false;
+    }
+
+    // trim down to a single path separator at the end, in case the caller added one or more.
+    pathwithseplen--;
+    while ((pathwithseplen >= 0) && (pathwithsep[pathwithseplen] == '/')) {
+        pathwithsep[pathwithseplen--] = '\0';
+    }
 
-    DIR *dir = opendir(path);
+    DIR *dir = opendir(pathwithsep);
     if (!dir) {
+        SDL_free(pathwithsep);
         return SDL_SetError("Can't open directory: %s", strerror(errno));
     }
 
+    // make sure there's a path separator at the end now for the actual callback.
+    pathwithsep[++pathwithseplen] = '/';
+    pathwithsep[++pathwithseplen] = '\0';
+
+    SDL_EnumerationResult result = SDL_ENUM_CONTINUE;
     struct dirent *ent;
-    while ((result == SDL_ENUM_CONTINUE) && ((ent = readdir(dir)) != NULL))
-    {
+    while ((result == SDL_ENUM_CONTINUE) && ((ent = readdir(dir)) != NULL)) {
         const char *name = ent->d_name;
         if ((SDL_strcmp(name, ".") == 0) || (SDL_strcmp(name, "..") == 0)) {
             continue;
         }
-        result = cb(userdata, path, name);
+        result = cb(userdata, pathwithsep, name);
     }
 
     closedir(dir);
 
+    SDL_free(pathwithsep);
+
     return (result != SDL_ENUM_FAILURE);
 }
 
diff --git a/src/filesystem/windows/SDL_sysfsops.c b/src/filesystem/windows/SDL_sysfsops.c
index 89961c6a8fa8e..9c48ba957b599 100644
--- a/src/filesystem/windows/SDL_sysfsops.c
+++ b/src/filesystem/windows/SDL_sysfsops.c
@@ -34,35 +34,45 @@ bool SDL_SYS_EnumerateDirectory(const char *path, SDL_EnumerateDirectoryCallback
     SDL_EnumerationResult result = SDL_ENUM_CONTINUE;
     if (*path == '\0') {  // if empty (completely at the root), we need to enumerate drive letters.
         const DWORD drives = GetLogicalDrives();
-        char name[3] = { 0, ':', '\0' };
+        char name[] = { 0, ':', '\\', '\0' };
         for (int i = 'A'; (result == SDL_ENUM_CONTINUE) && (i <= 'Z'); i++) {
             if (drives & (1 << (i - 'A'))) {
                 name[0] = (char) i;
-                result = cb(userdata, path, name);
+                result = cb(userdata, "", name);
             }
         }
     } else {
-        const size_t patternlen = SDL_strlen(path) + 3;
-        char *pattern = (char *) SDL_malloc(patternlen);
-        if (!pattern) {
-            return false;
-        }
-
         // you need a wildcard to enumerate through FindFirstFileEx(), but the wildcard is only checked in the
         // filename element at the end of the path string, so always tack on a "\\*" to get everything, and
         // also prevent any wildcards inserted by the app from being respected.
-        SDL_snprintf(pattern, patternlen, "%s\\*", path);
+        char *pattern = NULL;
+        int patternlen = SDL_asprintf(&pattern, "%s\\\\", path);  // we'll replace that second '\\' in the trimdown.
+        if ((patternlen == -1) || (!pattern)) {
+            return false;
+        }
+
+        // trim down to a single path separator at the end, in case the caller added one or more.
+        patternlen--;
+        while ((patternlen >= 0) && ((pattern[patternlen] == '\\') || (pattern[patternlen] == '/'))) {
+            pattern[patternlen--] ='\0';
+        }
+        pattern[++patternlen] = '\\';
+        pattern[++patternlen] = '*';
+        pattern[++patternlen] = '\0';
 
         WCHAR *wpattern = WIN_UTF8ToStringW(pattern);
-        SDL_free(pattern);
         if (!wpattern) {
+            SDL_free(pattern);
             return false;
         }
 
+        pattern[--patternlen] = '\0';  // chop off the '*' so we just have the dirname with a path separator.
+
         WIN32_FIND_DATAW entw;
         HANDLE dir = FindFirstFileExW(wpattern, FindExInfoStandard, &entw, FindExSearchNameMatch, NULL, 0);
         SDL_free(wpattern);
         if (dir == INVALID_HANDLE_VALUE) {
+            SDL_free(pattern);
             return WIN_SetError("Failed to enumerate directory");
         }
 
@@ -79,12 +89,13 @@ bool SDL_SYS_EnumerateDirectory(const char *path, SDL_EnumerateDirectoryCallback
             if (!utf8fn) {
                 result = SDL_ENUM_FAILURE;
             } else {
-                result = cb(userdata, path, utf8fn);
+                result = cb(userdata, pattern, utf8fn);
                 SDL_free(utf8fn);
             }
         } while ((result == SDL_ENUM_CONTINUE) && (FindNextFileW(dir, &entw) != 0));
 
         FindClose(dir);
+        SDL_free(pattern);
     }
 
     return (result != SDL_ENUM_FAILURE);
diff --git a/test/testfilesystem.c b/test/testfilesystem.c
index 5618413730bcf..9fe2cdb82383e 100644
--- a/test/testfilesystem.c
+++ b/test/testfilesystem.c
@@ -20,14 +20,7 @@ static SDL_EnumerationResult SDLCALL enum_callback(void *userdata, const char *o
     SDL_PathInfo info;
     char *fullpath = NULL;
 
-    /* you can use '/' for a path separator on Windows, but to make the log output look correct, we'll #ifdef this... */
-    #ifdef SDL_PLATFORM_WINDOWS
-    const char *pathsep = "\\";
-    #else
-    const char *pathsep = "/";
-    #endif
-
-    if (SDL_asprintf(&fullpath, "%s%s%s", origdir, *origdir ? pathsep : "", fname) < 0) {
+    if (SDL_asprintf(&fullpath, "%s%s", origdir, fname) < 0) {
         SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Out of memory!");
         return SDL_ENUM_FAILURE;
     }