[php-src] master: zend_string: Add `zend_string_ends_with*()` (#22819)

Tim Düsterhus via GitHub <[email protected]> Mon, 20 Jul 2026 10:58:43 +0000
Newsgroups gmane.comp.php.cvs.general
Message-ID <[email protected]>
Author: Tim Düsterhus (TimWolla)
Committer: GitHub (web-flow)
Pusher: TimWolla
Date: 2026-07-20T12:58:40+02:00

Commit: https://github.com/php/php-src/commit/d37a820d59fa2a870f90f52fc545c27f734525ab
Raw diff: https://github.com/php/php-src/commit/d37a820d59fa2a870f90f52fc545c27f734525ab.diff

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

Changed paths:
  M  UPGRADING.INTERNALS
  M  Zend/zend_string.h
  M  ext/standard/string.c


Diff:

diff --git a/UPGRADING.INTERNALS b/UPGRADING.INTERNALS
index 79d5def33823..ae58e7ba8d5b 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 cb0502891efa..d9efea00e7bd 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 c6e89dcca2e5..823c32a8cf1e 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));
 }
 /* }}} */