SDL: Added SDL_strnstr()

From ac0751a652741d6b54e48ff8e6955cf6fd7f7058 Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Sun, 3 Dec 2023 14:42:28 -0800
Subject: [PATCH] Added SDL_strnstr()

---
 CMakeLists.txt                                |  2 +-
 include/SDL3/SDL_stdinc.h                     |  2 +
 include/build_config/SDL_build_config.h.cmake |  1 +
 src/dynapi/SDL_dynapi.sym                     |  2 +
 src/dynapi/SDL_dynapi_overrides.h             |  2 +
 src/dynapi/SDL_dynapi_procs.h                 |  2 +
 src/stdlib/SDL_string.c                       | 42 ++++++++---
 test/testautomation_stdlib.c                  | 75 ++++++++++++++++++-
 8 files changed, 113 insertions(+), 15 deletions(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index fab3c11e2064..8af562830bdb 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1065,7 +1065,7 @@ if(SDL_LIBC)
     realloc rindex round roundf
     scalbn scalbnf setenv sin sinf sqr sqrt sqrtf sscanf strchr
     strcmp strlcat strlcpy strlen strncmp strnlen
-    strrchr strstr strtod strtok_r strtol strtoll strtoul strtoull
+    strrchr strstr strnstr strtod strtok_r strtol strtoll strtoul strtoull
     tan tanf trunc truncf
     unsetenv
     vsnprintf vsscanf
diff --git a/include/SDL3/SDL_stdinc.h b/include/SDL3/SDL_stdinc.h
index 50d81825c4ef..a257513f3aea 100644
--- a/include/SDL3/SDL_stdinc.h
+++ b/include/SDL3/SDL_stdinc.h
@@ -546,6 +546,7 @@ extern DECLSPEC size_t SDLCALL SDL_wcslcpy(SDL_OUT_Z_CAP(maxlen) wchar_t *dst, c
 extern DECLSPEC size_t SDLCALL SDL_wcslcat(SDL_INOUT_Z_CAP(maxlen) wchar_t *dst, const wchar_t *src, size_t maxlen);
 extern DECLSPEC wchar_t *SDLCALL SDL_wcsdup(const wchar_t *wstr);
 extern DECLSPEC wchar_t *SDLCALL SDL_wcsstr(const wchar_t *haystack, const wchar_t *needle);
+extern DECLSPEC wchar_t *SDLCALL SDL_wcsnstr(const wchar_t *haystack, const wchar_t *needle, size_t maxlen);
 
 extern DECLSPEC int SDLCALL SDL_wcscmp(const wchar_t *str1, const wchar_t *str2);
 extern DECLSPEC int SDLCALL SDL_wcsncmp(const wchar_t *str1, const wchar_t *str2, size_t maxlen);
@@ -566,6 +567,7 @@ extern DECLSPEC char *SDLCALL SDL_strlwr(char *str);
 extern DECLSPEC char *SDLCALL SDL_strchr(const char *str, int c);
 extern DECLSPEC char *SDLCALL SDL_strrchr(const char *str, int c);
 extern DECLSPEC char *SDLCALL SDL_strstr(const char *haystack, const char *needle);
+extern DECLSPEC char *SDLCALL SDL_strnstr(const char *haystack, const char *needle, size_t maxlen);
 extern DECLSPEC char *SDLCALL SDL_strcasestr(const char *haystack, const char *needle);
 extern DECLSPEC char *SDLCALL SDL_strtok_r(char *s1, const char *s2, char **saveptr);
 extern DECLSPEC size_t SDLCALL SDL_utf8strlen(const char *str);
diff --git a/include/build_config/SDL_build_config.h.cmake b/include/build_config/SDL_build_config.h.cmake
index 4c5f9b31c0db..b725a98e1480 100644
--- a/include/build_config/SDL_build_config.h.cmake
+++ b/include/build_config/SDL_build_config.h.cmake
@@ -116,6 +116,7 @@
 #cmakedefine HAVE_STRCHR 1
 #cmakedefine HAVE_STRRCHR 1
 #cmakedefine HAVE_STRSTR 1
+#cmakedefine HAVE_STRNSTR 1
 #cmakedefine HAVE_STRTOK_R 1
 #cmakedefine HAVE_ITOA 1
 #cmakedefine HAVE__LTOA 1
diff --git a/src/dynapi/SDL_dynapi.sym b/src/dynapi/SDL_dynapi.sym
index e007e8d4a3a4..61bf3069149e 100644
--- a/src/dynapi/SDL_dynapi.sym
+++ b/src/dynapi/SDL_dynapi.sym
@@ -960,6 +960,8 @@ SDL3_0.0.0 {
     SDL_GetGamepadMappings;
     SDL_GetTouchDevices;
     SDL_GetTouchDeviceName;
+    SDL_strnstr;
+    SDL_wcsnstr;
     # extra symbols go here (don't modify this line)
   local: *;
 };
diff --git a/src/dynapi/SDL_dynapi_overrides.h b/src/dynapi/SDL_dynapi_overrides.h
index 9c9696d445ad..bc03b31787fd 100644
--- a/src/dynapi/SDL_dynapi_overrides.h
+++ b/src/dynapi/SDL_dynapi_overrides.h
@@ -985,3 +985,5 @@
 #define SDL_GetGamepadMappings SDL_GetGamepadMappings_REAL
 #define SDL_GetTouchDevices SDL_GetTouchDevices_REAL
 #define SDL_GetTouchDeviceName SDL_GetTouchDeviceName_REAL
+#define SDL_strnstr SDL_strnstr_REAL
+#define SDL_wcsnstr SDL_wcsnstr_REAL
diff --git a/src/dynapi/SDL_dynapi_procs.h b/src/dynapi/SDL_dynapi_procs.h
index e055c8d06fe8..02d9aebae5be 100644
--- a/src/dynapi/SDL_dynapi_procs.h
+++ b/src/dynapi/SDL_dynapi_procs.h
@@ -1010,3 +1010,5 @@ SDL_DYNAPI_PROC(SDL_Renderer*,SDL_CreateRendererWithProperties,(SDL_PropertiesID
 SDL_DYNAPI_PROC(char**,SDL_GetGamepadMappings,(int *a),(a),return)
 SDL_DYNAPI_PROC(SDL_TouchID*,SDL_GetTouchDevices,(int *a),(a),return)
 SDL_DYNAPI_PROC(const char*,SDL_GetTouchDeviceName,(SDL_TouchID a),(a),return)
+SDL_DYNAPI_PROC(char*,SDL_strnstr,(const char *a, const char *b, size_t c),(a,b,c),return)
+SDL_DYNAPI_PROC(wchar_t*,SDL_wcsnstr,(const wchar_t *a, const wchar_t *b, size_t c),(a,b,c),return)
diff --git a/src/stdlib/SDL_string.c b/src/stdlib/SDL_string.c
index cdc51ef8db50..8570d4fa1ac5 100644
--- a/src/stdlib/SDL_string.c
+++ b/src/stdlib/SDL_string.c
@@ -468,19 +468,28 @@ wchar_t *SDL_wcsdup(const wchar_t *string)
     return newstr;
 }
 
-wchar_t *SDL_wcsstr(const wchar_t *haystack, const wchar_t *needle)
+wchar_t *SDL_wcsnstr(const wchar_t *haystack, const wchar_t *needle, size_t maxlen)
 {
-#ifdef HAVE_WCSSTR
-    return SDL_const_cast(wchar_t *, wcsstr(haystack, needle));
-#else
     size_t length = SDL_wcslen(needle);
-    while (*haystack) {
-        if (SDL_wcsncmp(haystack, needle, length) == 0) {
+    if (length == 0) {
+        return (wchar_t *)haystack;
+    }
+    while (maxlen >= length && *haystack) {
+        if (maxlen >= length && SDL_wcsncmp(haystack, needle, length) == 0) {
             return (wchar_t *)haystack;
         }
         ++haystack;
+        --maxlen;
     }
     return NULL;
+}
+
+wchar_t *SDL_wcsstr(const wchar_t *haystack, const wchar_t *needle)
+{
+#ifdef HAVE_WCSSTR
+    return SDL_const_cast(wchar_t *, wcsstr(haystack, needle));
+#else
+    return SDL_wcsnstr(haystack, needle, SDL_wcslen(haystack));
 #endif /* HAVE_WCSSTR */
 }
 
@@ -821,22 +830,35 @@ char *SDL_strrchr(const char *string, int c)
 #endif /* HAVE_STRRCHR */
 }
 
-char *SDL_strstr(const char *haystack, const char *needle)
+char *SDL_strnstr(const char *haystack, const char *needle, size_t maxlen)
 {
-#ifdef HAVE_STRSTR
-    return SDL_const_cast(char *, strstr(haystack, needle));
+#ifdef HAVE_STRNSTR
+    return SDL_const_cast(char *, strnstr(haystack, needle, maxlen));
 #else
     size_t length = SDL_strlen(needle);
-    while (*haystack) {
+    if (length == 0) {
+        return (char *)haystack;
+    }
+    while (maxlen >= length && *haystack) {
         if (SDL_strncmp(haystack, needle, length) == 0) {
             return (char *)haystack;
         }
         ++haystack;
+        --maxlen;
     }
     return NULL;
 #endif /* HAVE_STRSTR */
 }
 
+char *SDL_strstr(const char *haystack, const char *needle)
+{
+#ifdef HAVE_STRSTR
+    return SDL_const_cast(char *, strstr(haystack, needle));
+#else
+    return SDL_strnstr(haystack, needle, SDL_strlen(haystack));
+#endif /* HAVE_STRSTR */
+}
+
 char *SDL_strcasestr(const char *haystack, const char *needle)
 {
 #ifdef HAVE_STRCASESTR
diff --git a/test/testautomation_stdlib.c b/test/testautomation_stdlib.c
index eab676d795a6..d4abe2a2b277 100644
--- a/test/testautomation_stdlib.c
+++ b/test/testautomation_stdlib.c
@@ -60,6 +60,68 @@ static int stdlib_strlcpy(void *arg)
     return TEST_COMPLETED;
 }
 
+/**
+ * Call to SDL_strstr
+ */
+static int stdlib_strstr(void *arg)
+{
+    char *result;
+    const char *text = "abcdef";
+    const char *expected;
+
+    result = SDL_strstr(text, "");
+    expected = text;
+    SDLTest_AssertPass("Call to SDL_strstr(text, \"\")");
+    SDLTest_AssertCheck(result == expected, "Check result, expected: %s, got: %s", expected, result);
+
+    result = SDL_strstr(text, "abc");
+    expected = text;
+    SDLTest_AssertPass("Call to SDL_strstr(text, \"abc\")");
+    SDLTest_AssertCheck(result == expected, "Check result, expected: %s, got: %s", expected, result);
+
+    result = SDL_strstr(text, "bcd");
+    expected = text+1;
+    SDLTest_AssertPass("Call to SDL_strstr(text, \"bcd\")");
+    SDLTest_AssertCheck(result == expected, "Check result, expected: %s, got: %s", expected, result);
+
+    result = SDL_strstr(text, "xyz");
+    expected = NULL;
+    SDLTest_AssertPass("Call to SDL_strstr(text, \"xyz\")");
+    SDLTest_AssertCheck(result == expected, "Check result, expected: (null), got: %s", result);
+
+    result = SDL_strnstr(text, "", SDL_strlen(text));
+    expected = text;
+    SDLTest_AssertPass("Call to SDL_strnstr(text, \"\", SDL_strlen(text))");
+    SDLTest_AssertCheck(result == expected, "Check result, expected: %s, got: %s", expected, result);
+
+    result = SDL_strnstr(text, "abc", SDL_strlen(text));
+    expected = text;
+    SDLTest_AssertPass("Call to SDL_strnstr(text, \"abc\", SDL_strlen(text))");
+    SDLTest_AssertCheck(result == expected, "Check result, expected: %s, got: %s", expected, result);
+
+    result = SDL_strnstr(text, "bcd", SDL_strlen(text));
+    expected = text+1;
+    SDLTest_AssertPass("Call to SDL_strnstr(text, \"bcd\", SDL_strlen(text))");
+    SDLTest_AssertCheck(result == expected, "Check result, expected: %s, got: %s", expected, result);
+
+    result = SDL_strnstr(text, "bcd", 3);
+    expected = NULL;
+    SDLTest_AssertPass("Call to SDL_strnstr(text, \"bcd\", 3)");
+    SDLTest_AssertCheck(result == expected, "Check result, expected: (null), got: %s", result);
+
+    result = SDL_strnstr(text, "xyz", 3);
+    expected = NULL;
+    SDLTest_AssertPass("Call to SDL_strnstr(text, \"xyz\", 3)");
+    SDLTest_AssertCheck(result == expected, "Check result, expected: (null), got: %s", result);
+
+    result = SDL_strnstr(text, "xyz", SDL_strlen(text)*100000);
+    expected = NULL;
+    SDLTest_AssertPass("Call to SDL_strnstr(text, \"xyz\", SDL_strlen(text)*100000)");
+    SDLTest_AssertCheck(result == expected, "Check result, expected: (null), got: %s", result);
+
+    return TEST_COMPLETED;
+}
+
 #if defined(HAVE_WFORMAT) || defined(HAVE_WFORMAT_EXTRA_ARGS)
 #pragma GCC diagnostic push
 #ifdef HAVE_WFORMAT
@@ -930,22 +992,26 @@ static const SDLTest_TestCaseReference stdlibTest2 = {
 };
 
 static const SDLTest_TestCaseReference stdlibTest3 = {
-    stdlib_snprintf, "stdlib_snprintf", "Call to SDL_snprintf", TEST_ENABLED
+    stdlib_strstr, "stdlib_strstr", "Call to SDL_strstr", TEST_ENABLED
 };
 
 static const SDLTest_TestCaseReference stdlibTest4 = {
-    stdlib_swprintf, "stdlib_swprintf", "Call to SDL_swprintf", TEST_ENABLED
+    stdlib_snprintf, "stdlib_snprintf", "Call to SDL_snprintf", TEST_ENABLED
 };
 
 static const SDLTest_TestCaseReference stdlibTest5 = {
-    stdlib_getsetenv, "stdlib_getsetenv", "Call to SDL_getenv and SDL_setenv", TEST_ENABLED
+    stdlib_swprintf, "stdlib_swprintf", "Call to SDL_swprintf", TEST_ENABLED
 };
 
 static const SDLTest_TestCaseReference stdlibTest6 = {
-    stdlib_sscanf, "stdlib_sscanf", "Call to SDL_sscanf", TEST_ENABLED
+    stdlib_getsetenv, "stdlib_getsetenv", "Call to SDL_getenv and SDL_setenv", TEST_ENABLED
 };
 
 static const SDLTest_TestCaseReference stdlibTest7 = {
+    stdlib_sscanf, "stdlib_sscanf", "Call to SDL_sscanf", TEST_ENABLED
+};
+
+static const SDLTest_TestCaseReference stdlibTest8 = {
     stdlib_aligned_alloc, "stdlib_aligned_alloc", "Call to SDL_aligned_alloc", TEST_ENABLED
 };
 
@@ -962,6 +1028,7 @@ static const SDLTest_TestCaseReference *stdlibTests[] = {
     &stdlibTest5,
     &stdlibTest6,
     &stdlibTest7,
+    &stdlibTest8,
     &stdlibTestOverflow,
     NULL
 };