Commit d37a820d59f for php.net

commit d37a820d59fa2a870f90f52fc545c27f734525ab
Author: Tim Düsterhus <tim@tideways-gmbh.com>
Date:   Mon Jul 20 12:58:40 2026 +0200

    zend_string: Add `zend_string_ends_with*()` (#22819)

diff --git a/UPGRADING.INTERNALS b/UPGRADING.INTERNALS
index 79d5def3382..ae58e7ba8d5 100644
--- a/UPGRADING.INTERNALS
+++ b/UPGRADING.INTERNALS
@@ -145,6 +145,7 @@ PHP 8.6 INTERNALS UPGRADE NOTES
     (sendfile, splice, copy_file_range, TransmitFile), and is now used by
     php_stream_copy_to_stream_ex(). The mmap-based copy fallback was removed.
   . Added zend_string_equals_cstr_ci().
+  . Added zend_string_ends_with() and related variants.

 ========================
 2. Build system changes
diff --git a/Zend/zend_string.h b/Zend/zend_string.h
index cb0502891ef..d9efea00e7b 100644
--- a/Zend/zend_string.h
+++ b/Zend/zend_string.h
@@ -493,6 +493,32 @@ static zend_always_inline bool zend_string_starts_with_ci(const zend_string *str
 #define zend_string_starts_with_literal_ci(str, prefix) \
 	zend_string_starts_with_cstr_ci(str, "" prefix, sizeof(prefix) - 1)

+static zend_always_inline bool zend_string_ends_with_cstr(const zend_string *str, const char *suffix, size_t suffix_length)
+{
+	return ZSTR_LEN(str) >= suffix_length && !memcmp(ZSTR_VAL(str) + ZSTR_LEN(str) - suffix_length, suffix, suffix_length);
+}
+
+static zend_always_inline bool zend_string_ends_with(const zend_string *str, const zend_string *suffix)
+{
+	return zend_string_ends_with_cstr(str, ZSTR_VAL(suffix), ZSTR_LEN(suffix));
+}
+
+#define zend_string_ends_with_literal(str, suffix) \
+	zend_string_ends_with_cstr(str, "" suffix, sizeof(suffix) - 1)
+
+static zend_always_inline bool zend_string_ends_with_cstr_ci(const zend_string *str, const char *suffix, size_t suffix_length)
+{
+	return ZSTR_LEN(str) >= suffix_length && !strncasecmp(ZSTR_VAL(str) + ZSTR_LEN(str) - suffix_length, suffix, suffix_length);
+}
+
+static zend_always_inline bool zend_string_ends_with_ci(const zend_string *str, const zend_string *suffix)
+{
+	return zend_string_ends_with_cstr_ci(str, ZSTR_VAL(suffix), ZSTR_LEN(suffix));
+}
+
+#define zend_string_ends_with_literal_ci(str, suffix) \
+	zend_string_ends_with_cstr_ci(str, "" suffix, sizeof(suffix) - 1)
+
 /*
  * DJBX33A (Daniel J. Bernstein, Times 33 with Addition)
  *
diff --git a/ext/standard/string.c b/ext/standard/string.c
index c6e89dcca2e..823c32a8cf1 100644
--- a/ext/standard/string.c
+++ b/ext/standard/string.c
@@ -1861,13 +1861,7 @@ PHP_FUNCTION(str_ends_with)
 		Z_PARAM_STR(needle)
 	ZEND_PARSE_PARAMETERS_END();

-	if (ZSTR_LEN(needle) > ZSTR_LEN(haystack)) {
-		RETURN_FALSE;
-	}
-
-	RETURN_BOOL(memcmp(
-		ZSTR_VAL(haystack) + ZSTR_LEN(haystack) - ZSTR_LEN(needle),
-		ZSTR_VAL(needle), ZSTR_LEN(needle)) == 0);
+	RETURN_BOOL(zend_string_ends_with(haystack, needle));
 }
 /* }}} */