SDL: dialog: Cocoa shouldn't crash if there's a '.' in the filters. (2f3d2)

From 2f3d242183b1a34b5e98005c7b7aa24a7ae7582d Mon Sep 17 00:00:00 2001
From: "Ryan C. Gordon" <[EMAIL REDACTED]>
Date: Thu, 1 May 2025 18:11:36 -0400
Subject: [PATCH] dialog: Cocoa shouldn't crash if there's a '.' in the
 filters.

So something like "index.pb" will now accept any file with a ".pb" extension,
to make macOS happy. This seems like a reasonable tradeoff.

Other minor cleanups.

Fixes #12778.

(cherry picked from commit 691cc5bb5e95b4c101d874b29dd112bced86e598)
---
 src/dialog/cocoa/SDL_cocoadialog.m | 35 ++++++++++++++++++++----------
 test/testdialog.c                  |  8 +++----
 2 files changed, 28 insertions(+), 15 deletions(-)

diff --git a/src/dialog/cocoa/SDL_cocoadialog.m b/src/dialog/cocoa/SDL_cocoadialog.m
index fb9c5ad88f77b..a6054a6ebae7f 100644
--- a/src/dialog/cocoa/SDL_cocoadialog.m
+++ b/src/dialog/cocoa/SDL_cocoadialog.m
@@ -27,6 +27,26 @@
 #import <Cocoa/Cocoa.h>
 #import <UniformTypeIdentifiers/UTType.h>
 
+static void AddFileExtensionType(NSMutableArray *types, const char *pattern_ptr)
+{
+    if (!*pattern_ptr) {
+        return;  // in case the string had an extra ';' at the end.
+    }
+
+    // -[UTType typeWithFilenameExtension] will return nil if there's a period in the string. It's better to
+    //  allow too many files than not allow the one the user actually needs, so just take the part after the '.'
+    const char *dot = SDL_strrchr(pattern_ptr, '.');
+    NSString *extstr = [NSString stringWithFormat: @"%s", dot ? (dot + 1) : pattern_ptr];
+    if (@available(macOS 11.0, *)) {
+        UTType *uttype = [UTType typeWithFilenameExtension:extstr];
+        if (uttype) {  // still failed? Don't add the pattern. This is what the pre-macOS11 path does internally anyhow.
+            [types addObject:uttype];
+        }
+    } else {
+        [types addObject:extstr];
+    }
+}
+
 void SDL_SYS_ShowFileDialogWithProperties(SDL_FileDialogType type, SDL_DialogFileCallback callback, void *userdata, SDL_PropertiesID props)
 {
     SDL_Window* window = SDL_GetPointerProperty(props, SDL_PROP_FILE_DIALOG_WINDOW_POINTER, NULL);
@@ -87,7 +107,7 @@ void SDL_SYS_ShowFileDialogWithProperties(SDL_FileDialogType type, SDL_DialogFil
 
     if (filters) {
         // On macOS 11.0 and up, this is an array of UTType. Prior to that, it's an array of NSString
-        NSMutableArray *types = [[NSMutableArray alloc] initWithCapacity:nfilters ];
+        NSMutableArray *types = [[NSMutableArray alloc] initWithCapacity:nfilters];
 
         int has_all_files = 0;
         for (int i = 0; i < nfilters; i++) {
@@ -102,21 +122,14 @@ void SDL_SYS_ShowFileDialogWithProperties(SDL_FileDialogType type, SDL_DialogFil
             for (char *c = pattern; *c; c++) {
                 if (*c == ';') {
                     *c = '\0';
-                    if(@available(macOS 11.0, *)) {
-                        [types addObject: [UTType typeWithFilenameExtension:[NSString stringWithFormat: @"%s", pattern_ptr]]];
-                    } else {
-                        [types addObject: [NSString stringWithFormat: @"%s", pattern_ptr]];
-                    }
+                    AddFileExtensionType(types, pattern_ptr);
                     pattern_ptr = c + 1;
                 } else if (*c == '*') {
                     has_all_files = 1;
                 }
             }
-            if(@available(macOS 11.0, *)) {
-                [types addObject: [UTType typeWithFilenameExtension:[NSString stringWithFormat: @"%s", pattern_ptr]]];
-            } else {
-                [types addObject: [NSString stringWithFormat: @"%s", pattern_ptr]];
-            }
+
+            AddFileExtensionType(types, pattern_ptr);  // get the last piece of the string.
 
             SDL_free(pattern);
         }
diff --git a/test/testdialog.c b/test/testdialog.c
index 3d19fb9416c15..dcff5bd9f00d5 100644
--- a/test/testdialog.c
+++ b/test/testdialog.c
@@ -15,8 +15,9 @@
 #include <SDL3/SDL_main.h>
 #include <SDL3/SDL_test.h>
 
-const SDL_DialogFileFilter filters[3] = {
+const SDL_DialogFileFilter filters[] = {
     { "All files", "*" },
+    { "SVI Session Indexes", "index;svi-index;index.pb" },
     { "JPG images", "jpg;jpeg" },
     { "PNG images", "png" }
 };
@@ -54,7 +55,6 @@ int main(int argc, char *argv[])
     const SDL_FRect open_folder_rect = { 370, 50, 220, 140 };
     int i;
     const char *initial_path = NULL;
-    const int nfilters = sizeof(filters) / sizeof(*filters);
 
     /* Initialize test framework */
     state = SDLTest_CommonCreateState(argv, 0);
@@ -112,11 +112,11 @@ int main(int argc, char *argv[])
                  * - Nonzero if the user is allowed to choose multiple entries (not for SDL_ShowSaveFileDialog)
                  */
                 if (SDL_PointInRectFloat(&p, &open_file_rect)) {
-                    SDL_ShowOpenFileDialog(callback, NULL, w, filters, nfilters, initial_path, 1);
+                    SDL_ShowOpenFileDialog(callback, NULL, w, filters, SDL_arraysize(filters), initial_path, 1);
                 } else if (SDL_PointInRectFloat(&p, &open_folder_rect)) {
                     SDL_ShowOpenFolderDialog(callback, NULL, w, initial_path, 1);
                 } else if (SDL_PointInRectFloat(&p, &save_file_rect)) {
-                    SDL_ShowSaveFileDialog(callback, NULL, w, filters, nfilters, initial_path);
+                    SDL_ShowSaveFileDialog(callback, NULL, w, filters, SDL_arraysize(filters), initial_path);
                 }
             }
         }