From 4181b6625276e20953770ce652cac89fb36b2326 Mon Sep 17 00:00:00 2001 From: Carlos Granados Date: Fri, 16 Jan 2026 17:28:49 +0100 Subject: [PATCH] Add functions that work with suffixes and prefixes --- ext/standard/basic_functions.stub.php | 36 ++++ ext/standard/basic_functions_arginfo.h | 40 ++++- ext/standard/string.c | 160 ++++++++++++++++++ ext/standard/tests/strings/add_prefix.phpt | 64 +++++++ ext/standard/tests/strings/add_suffix.phpt | 64 +++++++ ext/standard/tests/strings/remove_prefix.phpt | 64 +++++++ ext/standard/tests/strings/remove_suffix.phpt | 64 +++++++ .../tests/strings/replace_prefix.phpt | 66 ++++++++ .../tests/strings/replace_suffix.phpt | 66 ++++++++ 9 files changed, 623 insertions(+), 1 deletion(-) create mode 100644 ext/standard/tests/strings/add_prefix.phpt create mode 100644 ext/standard/tests/strings/add_suffix.phpt create mode 100644 ext/standard/tests/strings/remove_prefix.phpt create mode 100644 ext/standard/tests/strings/remove_suffix.phpt create mode 100644 ext/standard/tests/strings/replace_prefix.phpt create mode 100644 ext/standard/tests/strings/replace_suffix.phpt diff --git a/ext/standard/basic_functions.stub.php b/ext/standard/basic_functions.stub.php index e27dca069c55b..5654c366835e6 100644 --- a/ext/standard/basic_functions.stub.php +++ b/ext/standard/basic_functions.stub.php @@ -2429,6 +2429,42 @@ function str_starts_with(string $haystack, string $needle): bool {} /** @compile-time-eval */ function str_ends_with(string $haystack, string $needle): bool {} +/** + * @compile-time-eval + * @refcount 1 + */ +function add_prefix(string $source, string $prefix): string {} + +/** + * @compile-time-eval + * @refcount 1 + */ +function remove_prefix(string $source, string $prefix): string {} + +/** + * @compile-time-eval + * @refcount 1 + */ +function replace_prefix(string $source, string $prefix, string $replace): string {} + +/** + * @compile-time-eval + * @refcount 1 + */ +function add_suffix(string $source, string $suffix): string {} + +/** + * @compile-time-eval + * @refcount 1 + */ +function remove_suffix(string $source, string $suffix): string {} + +/** + * @compile-time-eval + * @refcount 1 + */ +function replace_suffix(string $source, string $suffix, string $replace): string {} + /** * @compile-time-eval * @refcount 1 diff --git a/ext/standard/basic_functions_arginfo.h b/ext/standard/basic_functions_arginfo.h index 6f202c01463fd..1c98be00d248c 100644 --- a/ext/standard/basic_functions_arginfo.h +++ b/ext/standard/basic_functions_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: 1a1667a5c59111f096a758d5bb4aa7cf3ec09cfe */ + * Stub hash: 6fa6fecba6790140c2398d34210126e6bf3e503a */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_set_time_limit, 0, 1, _IS_BOOL, 0) ZEND_ARG_TYPE_INFO(0, seconds, IS_LONG, 0) @@ -928,6 +928,32 @@ ZEND_END_ARG_INFO() #define arginfo_str_ends_with arginfo_str_contains +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_add_prefix, 0, 2, IS_STRING, 0) + ZEND_ARG_TYPE_INFO(0, source, IS_STRING, 0) + ZEND_ARG_TYPE_INFO(0, prefix, IS_STRING, 0) +ZEND_END_ARG_INFO() + +#define arginfo_remove_prefix arginfo_add_prefix + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_replace_prefix, 0, 3, IS_STRING, 0) + ZEND_ARG_TYPE_INFO(0, source, IS_STRING, 0) + ZEND_ARG_TYPE_INFO(0, prefix, IS_STRING, 0) + ZEND_ARG_TYPE_INFO(0, replace, IS_STRING, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_add_suffix, 0, 2, IS_STRING, 0) + ZEND_ARG_TYPE_INFO(0, source, IS_STRING, 0) + ZEND_ARG_TYPE_INFO(0, suffix, IS_STRING, 0) +ZEND_END_ARG_INFO() + +#define arginfo_remove_suffix arginfo_add_suffix + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_replace_suffix, 0, 3, IS_STRING, 0) + ZEND_ARG_TYPE_INFO(0, source, IS_STRING, 0) + ZEND_ARG_TYPE_INFO(0, suffix, IS_STRING, 0) + ZEND_ARG_TYPE_INFO(0, replace, IS_STRING, 0) +ZEND_END_ARG_INFO() + ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_chunk_split, 0, 1, IS_STRING, 0) ZEND_ARG_TYPE_INFO(0, string, IS_STRING, 0) ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, length, IS_LONG, 0, "76") @@ -2560,6 +2586,12 @@ ZEND_FUNCTION(strrchr); ZEND_FUNCTION(str_contains); ZEND_FUNCTION(str_starts_with); ZEND_FUNCTION(str_ends_with); +ZEND_FUNCTION(add_prefix); +ZEND_FUNCTION(remove_prefix); +ZEND_FUNCTION(replace_prefix); +ZEND_FUNCTION(add_suffix); +ZEND_FUNCTION(remove_suffix); +ZEND_FUNCTION(replace_suffix); ZEND_FUNCTION(chunk_split); ZEND_FUNCTION(substr); ZEND_FUNCTION(substr_replace); @@ -3162,6 +3194,12 @@ static const zend_function_entry ext_functions[] = { ZEND_RAW_FENTRY("str_contains", zif_str_contains, arginfo_str_contains, ZEND_ACC_COMPILE_TIME_EVAL, frameless_function_infos_str_contains, NULL) ZEND_RAW_FENTRY("str_starts_with", zif_str_starts_with, arginfo_str_starts_with, ZEND_ACC_COMPILE_TIME_EVAL, frameless_function_infos_str_starts_with, NULL) ZEND_RAW_FENTRY("str_ends_with", zif_str_ends_with, arginfo_str_ends_with, ZEND_ACC_COMPILE_TIME_EVAL, NULL, NULL) + ZEND_RAW_FENTRY("add_prefix", zif_add_prefix, arginfo_add_prefix, ZEND_ACC_COMPILE_TIME_EVAL, NULL, NULL) + ZEND_RAW_FENTRY("remove_prefix", zif_remove_prefix, arginfo_remove_prefix, ZEND_ACC_COMPILE_TIME_EVAL, NULL, NULL) + ZEND_RAW_FENTRY("replace_prefix", zif_replace_prefix, arginfo_replace_prefix, ZEND_ACC_COMPILE_TIME_EVAL, NULL, NULL) + ZEND_RAW_FENTRY("add_suffix", zif_add_suffix, arginfo_add_suffix, ZEND_ACC_COMPILE_TIME_EVAL, NULL, NULL) + ZEND_RAW_FENTRY("remove_suffix", zif_remove_suffix, arginfo_remove_suffix, ZEND_ACC_COMPILE_TIME_EVAL, NULL, NULL) + ZEND_RAW_FENTRY("replace_suffix", zif_replace_suffix, arginfo_replace_suffix, ZEND_ACC_COMPILE_TIME_EVAL, NULL, NULL) ZEND_RAW_FENTRY("chunk_split", zif_chunk_split, arginfo_chunk_split, ZEND_ACC_COMPILE_TIME_EVAL, NULL, NULL) ZEND_RAW_FENTRY("substr", zif_substr, arginfo_substr, ZEND_ACC_COMPILE_TIME_EVAL, frameless_function_infos_substr, NULL) ZEND_RAW_FENTRY("substr_replace", zif_substr_replace, arginfo_substr_replace, ZEND_ACC_COMPILE_TIME_EVAL, NULL, NULL) diff --git a/ext/standard/string.c b/ext/standard/string.c index 0b7d5be1a2576..931d08d2c1513 100644 --- a/ext/standard/string.c +++ b/ext/standard/string.c @@ -1889,6 +1889,166 @@ PHP_FUNCTION(str_ends_with) } /* }}} */ +/* {{{ Adds prefix to source string if source does not already start with prefix */ +PHP_FUNCTION(add_prefix) +{ + zend_string *source, *prefix; + + ZEND_PARSE_PARAMETERS_START(2, 2) + Z_PARAM_STR(source) + Z_PARAM_STR(prefix) + ZEND_PARSE_PARAMETERS_END(); + + if (ZSTR_LEN(prefix) == 0) { + RETURN_STR_COPY(source); + } + + if (ZSTR_LEN(prefix) <= ZSTR_LEN(source) && + memcmp(ZSTR_VAL(source), ZSTR_VAL(prefix), ZSTR_LEN(prefix)) == 0) { + RETURN_STR_COPY(source); + } + + zend_string *result = zend_string_alloc(ZSTR_LEN(prefix) + ZSTR_LEN(source), 0); + memcpy(ZSTR_VAL(result), ZSTR_VAL(prefix), ZSTR_LEN(prefix)); + memcpy(ZSTR_VAL(result) + ZSTR_LEN(prefix), ZSTR_VAL(source), ZSTR_LEN(source)); + ZSTR_VAL(result)[ZSTR_LEN(prefix) + ZSTR_LEN(source)] = '\0'; + RETURN_NEW_STR(result); +} +/* }}} */ + +/* {{{ Removes prefix from source string if source starts with prefix */ +PHP_FUNCTION(remove_prefix) +{ + zend_string *source, *prefix; + + ZEND_PARSE_PARAMETERS_START(2, 2) + Z_PARAM_STR(source) + Z_PARAM_STR(prefix) + ZEND_PARSE_PARAMETERS_END(); + + if (ZSTR_LEN(prefix) > ZSTR_LEN(source)) { + RETURN_STR_COPY(source); + } + + if (memcmp(ZSTR_VAL(source), ZSTR_VAL(prefix), ZSTR_LEN(prefix)) == 0) { + RETURN_STRINGL(ZSTR_VAL(source) + ZSTR_LEN(prefix), ZSTR_LEN(source) - ZSTR_LEN(prefix)); + } + + RETURN_STR_COPY(source); +} +/* }}} */ + +/* {{{ Replaces prefix in source string if source starts with prefix */ +PHP_FUNCTION(replace_prefix) +{ + zend_string *source, *prefix, *replace; + + ZEND_PARSE_PARAMETERS_START(3, 3) + Z_PARAM_STR(source) + Z_PARAM_STR(prefix) + Z_PARAM_STR(replace) + ZEND_PARSE_PARAMETERS_END(); + + if (ZSTR_LEN(prefix) > ZSTR_LEN(source)) { + RETURN_STR_COPY(source); + } + + if (memcmp(ZSTR_VAL(source), ZSTR_VAL(prefix), ZSTR_LEN(prefix)) == 0) { + size_t remaining_len = ZSTR_LEN(source) - ZSTR_LEN(prefix); + zend_string *result = zend_string_alloc(ZSTR_LEN(replace) + remaining_len, 0); + memcpy(ZSTR_VAL(result), ZSTR_VAL(replace), ZSTR_LEN(replace)); + memcpy(ZSTR_VAL(result) + ZSTR_LEN(replace), ZSTR_VAL(source) + ZSTR_LEN(prefix), remaining_len); + ZSTR_VAL(result)[ZSTR_LEN(replace) + remaining_len] = '\0'; + RETURN_NEW_STR(result); + } + + RETURN_STR_COPY(source); +} +/* }}} */ + +/* {{{ Adds suffix to source string if source does not already end with suffix */ +PHP_FUNCTION(add_suffix) +{ + zend_string *source, *suffix; + + ZEND_PARSE_PARAMETERS_START(2, 2) + Z_PARAM_STR(source) + Z_PARAM_STR(suffix) + ZEND_PARSE_PARAMETERS_END(); + + if (ZSTR_LEN(suffix) == 0) { + RETURN_STR_COPY(source); + } + + if (ZSTR_LEN(suffix) <= ZSTR_LEN(source) && + memcmp( + ZSTR_VAL(source) + ZSTR_LEN(source) - ZSTR_LEN(suffix), + ZSTR_VAL(suffix), ZSTR_LEN(suffix)) == 0) { + RETURN_STR_COPY(source); + } + + zend_string *result = zend_string_alloc(ZSTR_LEN(source) + ZSTR_LEN(suffix), 0); + memcpy(ZSTR_VAL(result), ZSTR_VAL(source), ZSTR_LEN(source)); + memcpy(ZSTR_VAL(result) + ZSTR_LEN(source), ZSTR_VAL(suffix), ZSTR_LEN(suffix)); + ZSTR_VAL(result)[ZSTR_LEN(source) + ZSTR_LEN(suffix)] = '\0'; + RETURN_NEW_STR(result); +} +/* }}} */ + +/* {{{ Removes suffix from source string if source ends with suffix */ +PHP_FUNCTION(remove_suffix) +{ + zend_string *source, *suffix; + + ZEND_PARSE_PARAMETERS_START(2, 2) + Z_PARAM_STR(source) + Z_PARAM_STR(suffix) + ZEND_PARSE_PARAMETERS_END(); + + if (ZSTR_LEN(suffix) > ZSTR_LEN(source)) { + RETURN_STR_COPY(source); + } + + if (memcmp( + ZSTR_VAL(source) + ZSTR_LEN(source) - ZSTR_LEN(suffix), + ZSTR_VAL(suffix), ZSTR_LEN(suffix)) == 0) { + RETURN_STRINGL(ZSTR_VAL(source), ZSTR_LEN(source) - ZSTR_LEN(suffix)); + } + + RETURN_STR_COPY(source); +} +/* }}} */ + +/* {{{ Replaces suffix in source string if source ends with suffix */ +PHP_FUNCTION(replace_suffix) +{ + zend_string *source, *suffix, *replace; + + ZEND_PARSE_PARAMETERS_START(3, 3) + Z_PARAM_STR(source) + Z_PARAM_STR(suffix) + Z_PARAM_STR(replace) + ZEND_PARSE_PARAMETERS_END(); + + if (ZSTR_LEN(suffix) > ZSTR_LEN(source)) { + RETURN_STR_COPY(source); + } + + if (memcmp( + ZSTR_VAL(source) + ZSTR_LEN(source) - ZSTR_LEN(suffix), + ZSTR_VAL(suffix), ZSTR_LEN(suffix)) == 0) { + size_t base_len = ZSTR_LEN(source) - ZSTR_LEN(suffix); + zend_string *result = zend_string_alloc(base_len + ZSTR_LEN(replace), 0); + memcpy(ZSTR_VAL(result), ZSTR_VAL(source), base_len); + memcpy(ZSTR_VAL(result) + base_len, ZSTR_VAL(replace), ZSTR_LEN(replace)); + ZSTR_VAL(result)[base_len + ZSTR_LEN(replace)] = '\0'; + RETURN_NEW_STR(result); + } + + RETURN_STR_COPY(source); +} +/* }}} */ + static inline void _zend_strpos(zval *return_value, zend_string *haystack, zend_string *needle, zend_long offset) { const char *found = NULL; diff --git a/ext/standard/tests/strings/add_prefix.phpt b/ext/standard/tests/strings/add_prefix.phpt new file mode 100644 index 0000000000000..a5a73dc828262 --- /dev/null +++ b/ext/standard/tests/strings/add_prefix.phpt @@ -0,0 +1,64 @@ +--TEST-- +Test add_prefix() function +--FILE-- + +--EXPECTF-- +*** Testing add_prefix() : various strings *** +string(21) "TheBeginningMiddleEnd" +string(18) "BeginningMiddleEnd" +string(18) "BeginningMiddleEnd" +string(6) "prefix" +string(0) "" +string(1) " " +string(4) "abca" +string(4) "test" +string(19) "%0BeginningMiddleEnd" +string(1) "%0" +string(1) "%0" +string(2) "%0a" +string(2) "%0a" +string(7) "%0abc%0ab" +string(5) "c%0%0ab" +string(6) "a%0da%0b" +string(6) "a%0za%0b" diff --git a/ext/standard/tests/strings/add_suffix.phpt b/ext/standard/tests/strings/add_suffix.phpt new file mode 100644 index 0000000000000..d4ba56924dff9 --- /dev/null +++ b/ext/standard/tests/strings/add_suffix.phpt @@ -0,0 +1,64 @@ +--TEST-- +Test add_suffix() function +--FILE-- + +--EXPECTF-- +*** Testing add_suffix() : various strings *** +string(21) "BeginningMiddleEnding" +string(18) "BeginningMiddleEnd" +string(18) "BeginningMiddleEnd" +string(6) "suffix" +string(0) "" +string(1) " " +string(4) "aabc" +string(4) "test" +string(19) "BeginningMiddleEnd%0" +string(1) "%0" +string(1) "%0" +string(2) "a%0" +string(2) "a%0" +string(4) "ab%0c" +string(5) "ab%0%0c" +string(6) "b%0ad%0a" +string(6) "b%0az%0a" diff --git a/ext/standard/tests/strings/remove_prefix.phpt b/ext/standard/tests/strings/remove_prefix.phpt new file mode 100644 index 0000000000000..9fac56bbd0a39 --- /dev/null +++ b/ext/standard/tests/strings/remove_prefix.phpt @@ -0,0 +1,64 @@ +--TEST-- +Test remove_prefix() function +--FILE-- + +--EXPECTF-- +*** Testing remove_prefix() : various strings *** +string(9) "MiddleEnd" +string(18) "BeginningMiddleEnd" +string(18) "BeginningMiddleEnd" +string(0) "" +string(18) "BeginningMiddleEnd" +string(18) "BeginningMiddleEnd" +string(0) "" +string(0) "" +string(18) "BeginningMiddleEnd" +string(1) "%0" +string(0) "" +string(1) "a" +string(1) "a" +string(3) "b%0a" +string(3) "b%0a" +string(1) "a" +string(1) "a" diff --git a/ext/standard/tests/strings/remove_suffix.phpt b/ext/standard/tests/strings/remove_suffix.phpt new file mode 100644 index 0000000000000..84a80bb53adc0 --- /dev/null +++ b/ext/standard/tests/strings/remove_suffix.phpt @@ -0,0 +1,64 @@ +--TEST-- +Test remove_suffix() function +--FILE-- + +--EXPECTF-- +*** Testing remove_suffix() : various strings *** +string(15) "beginningMiddle" +string(18) "beginningMiddleEnd" +string(18) "beginningMiddleEnd" +string(0) "" +string(18) "beginningMiddleEnd" +string(18) "beginningMiddleEnd" +string(0) "" +string(0) "" +string(18) "beginningMiddleEnd" +string(1) "%0" +string(0) "" +string(1) "a" +string(1) "a" +string(3) "a%0b" +string(3) "a%0b" +string(1) "a" +string(1) "a" diff --git a/ext/standard/tests/strings/replace_prefix.phpt b/ext/standard/tests/strings/replace_prefix.phpt new file mode 100644 index 0000000000000..80cec7ca647d8 --- /dev/null +++ b/ext/standard/tests/strings/replace_prefix.phpt @@ -0,0 +1,66 @@ +--TEST-- +Test replace_prefix() function +--FILE-- + +--EXPECTF-- +*** Testing replace_prefix() : various strings *** +string(14) "StartMiddleEnd" +string(18) "BeginningMiddleEnd" +string(9) "MiddleEnd" +string(21) "NewBeginningMiddleEnd" +string(19) "https://example.com" +string(8) "/opt/bin" +string(2) "ab" +string(8) "replaced" +string(0) "" +string(18) "BeginningMiddleEnd" +string(2) "ba" +string(2) "%0a" +string(3) "dab" +string(4) "%0dab" +string(3) "b%0a" +string(5) "newst" diff --git a/ext/standard/tests/strings/replace_suffix.phpt b/ext/standard/tests/strings/replace_suffix.phpt new file mode 100644 index 0000000000000..5ccab1b7260b8 --- /dev/null +++ b/ext/standard/tests/strings/replace_suffix.phpt @@ -0,0 +1,66 @@ +--TEST-- +Test replace_suffix() function +--FILE-- + +--EXPECTF-- +*** Testing replace_suffix() : various strings *** +string(21) "BeginningMiddleFinish" +string(18) "BeginningMiddleEnd" +string(15) "BeginningMiddle" +string(21) "BeginningMiddleEndNew" +string(15) "file.backup.txt" +string(11) "document.md" +string(2) "ab" +string(8) "replaced" +string(0) "" +string(18) "BeginningMiddleEnd" +string(2) "ab" +string(2) "a%0" +string(3) "abd" +string(4) "ab%0d" +string(3) "a%0b" +string(5) "tenew"