diff --git a/ext/mbstring/mbstring.c b/ext/mbstring/mbstring.c index 4ca947374e69a..9f2ff185dcdd1 100644 --- a/ext/mbstring/mbstring.c +++ b/ext/mbstring/mbstring.c @@ -260,6 +260,30 @@ ZEND_BEGIN_ARG_INFO_EX(arginfo_mb_strripos, 0, 0, 2) ZEND_ARG_INFO(0, encoding) ZEND_END_ARG_INFO() +ZEND_BEGIN_ARG_INFO_EX(arginfo_mb_str_starts_with, 0, 0, 2) + ZEND_ARG_INFO(0, haystack) + ZEND_ARG_INFO(0, needle) + ZEND_ARG_INFO(0, encoding) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_mb_str_starts_with_ci, 0, 0, 2) + ZEND_ARG_INFO(0, haystack) + ZEND_ARG_INFO(0, needle) + ZEND_ARG_INFO(0, encoding) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_mb_str_ends_with, 0, 0, 2) + ZEND_ARG_INFO(0, haystack) + ZEND_ARG_INFO(0, needle) + ZEND_ARG_INFO(0, encoding) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_mb_str_ends_with_ci, 0, 0, 2) + ZEND_ARG_INFO(0, haystack) + ZEND_ARG_INFO(0, needle) + ZEND_ARG_INFO(0, encoding) +ZEND_END_ARG_INFO() + ZEND_BEGIN_ARG_INFO_EX(arginfo_mb_strstr, 0, 0, 2) ZEND_ARG_INFO(0, haystack) ZEND_ARG_INFO(0, needle) @@ -530,6 +554,10 @@ static const zend_function_entry mbstring_functions[] = { PHP_FE(mb_strrpos, arginfo_mb_strrpos) PHP_FE(mb_stripos, arginfo_mb_stripos) PHP_FE(mb_strripos, arginfo_mb_strripos) + PHP_FE(mb_str_starts_with, arginfo_mb_str_starts_with) + PHP_FE(mb_str_starts_with_ci, arginfo_mb_str_starts_with_ci) + PHP_FE(mb_str_ends_with, arginfo_mb_str_ends_with) + PHP_FE(mb_str_ends_with_ci, arginfo_mb_str_ends_with_ci) PHP_FE(mb_strstr, arginfo_mb_strstr) PHP_FE(mb_strrchr, arginfo_mb_strrchr) PHP_FE(mb_stristr, arginfo_mb_stristr) @@ -2581,6 +2609,192 @@ PHP_FUNCTION(mb_strripos) } /* }}} */ +/* {{{ proto boolean mb_str_starts_with(string haystack, string needle [, string encoding]) + Checks if haystack starts with needle */ +PHP_FUNCTION(mb_str_starts_with) +{ + mbfl_string haystack, needle; + zend_string *enc_name = NULL; + size_t n; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "ss|S", (char **)&haystack.val, &haystack.len, (char **)&needle.val, &needle.len, &enc_name) == FAILURE) { + return; + } + + haystack.no_language = needle.no_language = MBSTRG(language); + haystack.encoding = needle.encoding = php_mb_get_encoding(enc_name); + if (!haystack.encoding) { + RETURN_FALSE; + } + + if (needle.len == 0) { + RETURN_BOOL(1); + } + + n = mbfl_strpos(&haystack, &needle, 0, 0); + if (!mbfl_is_error(n)) { + if (n == 0) { + RETURN_BOOL(1); + } else { + RETURN_BOOL(0); + } + } else { + switch (-n) { + case 1: + break; + case 4: + php_error_docref(NULL, E_WARNING, "Unknown encoding or conversion error"); + break; + case 8: + php_error_docref(NULL, E_NOTICE, "Argument is empty"); + break; + default: + php_error_docref(NULL, E_WARNING, "Unknown error in mb_strpos"); + break; + } + RETURN_BOOL(0); + } +} +/* }}} */ + +/* {{{ proto boolean mb_str_starts_with_ci(string haystack, string needle. [, string encoding]) + Checks if haystack starts with needle, case insensitive */ +PHP_FUNCTION(mb_str_starts_with_ci) +{ + mbfl_string haystack, needle; + zend_string *enc_name = NULL; + size_t n; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "ss|S", (char **)&haystack.val, &haystack.len, (char **)&needle.val, &needle.len, &enc_name) == FAILURE) { + return; + } + + haystack.no_language = needle.no_language = MBSTRG(language); + haystack.encoding = needle.encoding = php_mb_get_encoding(enc_name); + if (!haystack.encoding) { + RETURN_FALSE; + } + + if (needle.len == 0) { + RETURN_BOOL(1); + } + + n = php_mb_stripos(0, (char *)haystack.val, haystack.len, (char *)needle.val, needle.len, 0, enc_name); + if (!mbfl_is_error(n)) { + if (n == 0) { + RETURN_BOOL(1); + } else { + RETURN_BOOL(0); + } + } else { + RETVAL_FALSE; + } +} +/* }}} */ + +/* {{{ proto boolean mb_str_ends_with(string haystack, string needle [, string encoding]) + Checks if haystack ends with needle */ +PHP_FUNCTION(mb_str_ends_with) +{ + mbfl_string haystack, needle; + zend_string *enc_name = NULL; + size_t n; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "ss|S", (char **)&haystack.val, &haystack.len, (char **)&needle.val, &needle.len, &enc_name) == FAILURE) { + return; + } + + haystack.no_language = needle.no_language = MBSTRG(language); + haystack.encoding = needle.encoding = php_mb_get_encoding(enc_name); + if (!haystack.encoding) { + RETURN_FALSE; + } + + if (needle.len == 0) { + RETURN_BOOL(1); + } + + if (needle.len > haystack.len) { + RETURN_BOOL(0); + } + + mbfl_string haystack_tail; + haystack_tail.encoding = haystack.encoding; + haystack_tail.no_language = haystack.no_language; + unsigned char* haystack_tail_val = haystack.val + sizeof(char) * (haystack.len - needle.len); + haystack_tail.val = haystack_tail_val; + haystack_tail.len = needle.len; + + n = mbfl_strpos(&haystack_tail, &needle, 0, 0); + if (!mbfl_is_error(n) && n == 0) { + RETURN_BOOL(1); + } else if (!mbfl_is_error(n)) { + RETURN_BOOL(0); + } else { + switch (-n) { + case 1: + break; + case 4: + php_error_docref(NULL, E_WARNING, "Unknown encoding or conversion error"); + break; + case 8: + php_error_docref(NULL, E_NOTICE, "Argument is empty"); + break; + case 16: + php_error_docref(NULL, E_WARNING, "Unknown needle, haystack error"); + break; + default: + php_error_docref(NULL, E_WARNING, "Unknown error in mb_strpos"); + break; + } + RETURN_BOOL(0); + } +} +/* }}} */ + +/* {{{ proto boolean mb_str_ends_with_ci(string haystack, string needle. [, string encoding]) + Checks if haystack ends with needle, case insensitive */ +PHP_FUNCTION(mb_str_ends_with_ci) +{ + mbfl_string haystack, needle; + zend_string *enc_name = NULL; + size_t n; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "ss|S", (char **)&haystack.val, &haystack.len, (char **)&needle.val, &needle.len, &enc_name) == FAILURE) { + return; + } + + haystack.no_language = needle.no_language = MBSTRG(language); + haystack.encoding = needle.encoding = php_mb_get_encoding(enc_name); + if (!haystack.encoding) { + RETURN_FALSE; + } + + if (needle.len == 0) { + RETURN_BOOL(1); + } + + mbfl_string haystack_tail; + haystack_tail.encoding = haystack.encoding; + haystack_tail.no_language = haystack.no_language; + unsigned char* haystack_tail_val = haystack.val + sizeof(char) * (haystack.len - needle.len); + haystack_tail.val = haystack_tail_val; + haystack_tail.len = needle.len; + + n = php_mb_stripos(0, (char *)haystack_tail.val, haystack_tail.len, (char *)needle.val, needle.len, 0, enc_name); + if (!mbfl_is_error(n)) { + if (n == 0) { + RETURN_BOOL(1); + } else { + RETURN_BOOL(0); + } + } else { + RETVAL_FALSE; + } +} +/* }}} */ + + /* {{{ proto string mb_strstr(string haystack, string needle[, bool part[, string encoding]]) Finds first occurrence of a string within another */ PHP_FUNCTION(mb_strstr) diff --git a/ext/mbstring/mbstring.h b/ext/mbstring/mbstring.h index 58ae9cebbc476..eb340252b397f 100644 --- a/ext/mbstring/mbstring.h +++ b/ext/mbstring/mbstring.h @@ -84,6 +84,10 @@ PHP_FUNCTION(mb_strpos); PHP_FUNCTION(mb_strrpos); PHP_FUNCTION(mb_stripos); PHP_FUNCTION(mb_strripos); +PHP_FUNCTION(mb_str_starts_with); +PHP_FUNCTION(mb_str_starts_with_ci); +PHP_FUNCTION(mb_str_ends_with); +PHP_FUNCTION(mb_str_ends_with_ci); PHP_FUNCTION(mb_strstr); PHP_FUNCTION(mb_strrchr); PHP_FUNCTION(mb_stristr); diff --git a/ext/mbstring/tests/mb_str_ends_with.phpt b/ext/mbstring/tests/mb_str_ends_with.phpt new file mode 100644 index 0000000000000..d2e8fe2ca8f1b --- /dev/null +++ b/ext/mbstring/tests/mb_str_ends_with.phpt @@ -0,0 +1,55 @@ +--TEST-- +mb_str_ends_with() +--SKIPIF-- + +--FILE-- + +--EXPECT-- +bool(true) +bool(false) +bool(true) +bool(false) +bool(true) +bool(false) +bool(false) +bool(true) +bool(false) +bool(true) +bool(false) diff --git a/ext/mbstring/tests/mb_str_ends_with_ci.phpt b/ext/mbstring/tests/mb_str_ends_with_ci.phpt new file mode 100644 index 0000000000000..fe3e45b4081ed --- /dev/null +++ b/ext/mbstring/tests/mb_str_ends_with_ci.phpt @@ -0,0 +1,53 @@ +--TEST-- +mb_str_ends_with_ci() +--SKIPIF-- + +--FILE-- + +--EXPECT-- +bool(true) +bool(false) +bool(true) +bool(false) +bool(true) +bool(false) +bool(true) +bool(false) +bool(true) +bool(false) diff --git a/ext/mbstring/tests/mb_str_starts_with.phpt b/ext/mbstring/tests/mb_str_starts_with.phpt new file mode 100644 index 0000000000000..c895ef5963041 --- /dev/null +++ b/ext/mbstring/tests/mb_str_starts_with.phpt @@ -0,0 +1,55 @@ +--TEST-- +mb_str_starts_with() +--SKIPIF-- + +--FILE-- + +--EXPECT-- +bool(true) +bool(false) +bool(true) +bool(false) +bool(true) +bool(false) +bool(false) +bool(true) +bool(false) +bool(true) +bool(false) diff --git a/ext/mbstring/tests/mb_str_starts_with_ci.phpt b/ext/mbstring/tests/mb_str_starts_with_ci.phpt new file mode 100644 index 0000000000000..b1081b97e5456 --- /dev/null +++ b/ext/mbstring/tests/mb_str_starts_with_ci.phpt @@ -0,0 +1,53 @@ +--TEST-- +mb_str_starts_with_ci() +--SKIPIF-- + +--FILE-- + +--EXPECT-- +bool(true) +bool(false) +bool(true) +bool(false) +bool(true) +bool(false) +bool(true) +bool(false) +bool(true) +bool(false) diff --git a/ext/standard/basic_functions.c b/ext/standard/basic_functions.c index f17133f7e17d7..85d5d77154faf 100644 --- a/ext/standard/basic_functions.c +++ b/ext/standard/basic_functions.c @@ -2236,6 +2236,26 @@ ZEND_BEGIN_ARG_INFO_EX(arginfo_strstr, 0, 0, 2) ZEND_ARG_INFO(0, part) ZEND_END_ARG_INFO() +ZEND_BEGIN_ARG_INFO_EX(arginfo_str_starts_with, 0, 0, 2) + ZEND_ARG_INFO(0, haystack) + ZEND_ARG_INFO(0, needle) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_str_starts_with_ci, 0, 0, 2) + ZEND_ARG_INFO(0, haystack) + ZEND_ARG_INFO(0, needle) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_str_ends_with, 0, 0, 2) + ZEND_ARG_INFO(0, haystack) + ZEND_ARG_INFO(0, needle) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_str_ends_with_ci, 0, 0, 2) + ZEND_ARG_INFO(0, haystack) + ZEND_ARG_INFO(0, needle) +ZEND_END_ARG_INFO() + ZEND_BEGIN_ARG_INFO_EX(arginfo_strpos, 0, 0, 2) ZEND_ARG_INFO(0, haystack) ZEND_ARG_INFO(0, needle) @@ -2775,6 +2795,10 @@ static const zend_function_entry basic_functions[] = { /* {{{ */ PHP_FE(strtoupper, arginfo_strtoupper) PHP_FE(strtolower, arginfo_strtolower) PHP_FE(strpos, arginfo_strpos) + PHP_FE(str_starts_with, arginfo_str_starts_with) + PHP_FE(str_starts_with_ci, arginfo_str_starts_with_ci) + PHP_FE(str_ends_with, arginfo_str_ends_with) + PHP_FE(str_ends_with_ci, arginfo_str_ends_with_ci) PHP_FE(stripos, arginfo_stripos) PHP_FE(strrpos, arginfo_strrpos) PHP_FE(strripos, arginfo_strripos) diff --git a/ext/standard/php_string.h b/ext/standard/php_string.h index 53209f4485199..c92c06e7d8d2c 100644 --- a/ext/standard/php_string.h +++ b/ext/standard/php_string.h @@ -41,6 +41,10 @@ PHP_FUNCTION(basename); PHP_FUNCTION(dirname); PHP_FUNCTION(pathinfo); PHP_FUNCTION(strstr); +PHP_FUNCTION(str_starts_with); +PHP_FUNCTION(str_starts_with_ci); +PHP_FUNCTION(str_ends_with); +PHP_FUNCTION(str_ends_with_ci); PHP_FUNCTION(strpos); PHP_FUNCTION(stripos); PHP_FUNCTION(strrpos); diff --git a/ext/standard/string.c b/ext/standard/string.c index bf2891c690900..db766b0b3115c 100644 --- a/ext/standard/string.c +++ b/ext/standard/string.c @@ -2301,6 +2301,86 @@ PHP_FUNCTION(substr) } /* }}} */ +/* {{{ proto boolean str_starts_with(string haystack, string needle) + Checks if haystack strats with needle */ +PHP_FUNCTION(str_starts_with) { + zend_string *haystack, *needle; + + ZEND_PARSE_PARAMETERS_START(2, 2) + Z_PARAM_STR(haystack) + Z_PARAM_STR(needle) + ZEND_PARSE_PARAMETERS_END(); + + if (needle->len > haystack->len) { + RETURN_FALSE; + } + + RETURN_BOOL(memcmp(ZSTR_VAL(haystack), ZSTR_VAL(needle), ZSTR_LEN(needle)) == 0); +} + +/* {{{ proto boolean str_starts_with_ci(string haystack, string needle) + Performs case insensitive check to determine if haystack starts with needle */ +PHP_FUNCTION(str_starts_with_ci) { + zend_string *haystack, *needle; + int i; + + ZEND_PARSE_PARAMETERS_START(2, 2) + Z_PARAM_STR(haystack) + Z_PARAM_STR(needle) + ZEND_PARSE_PARAMETERS_END(); + + if (needle->len > haystack->len) { + RETURN_FALSE; + } + + for (i = 0; i < needle->len; i++) + if (tolower(haystack->val[i]) != tolower(needle->val[i])) + RETURN_FALSE; + RETURN_TRUE; +} + +/* {{{ proto boolean str_ends_with(string haystack, string needle) + Checks if haystack ends with needle */ +PHP_FUNCTION(str_ends_with) { + zend_string *haystack, *needle; + int i, j; + + ZEND_PARSE_PARAMETERS_START(2, 2) + Z_PARAM_STR(haystack) + Z_PARAM_STR(needle) + ZEND_PARSE_PARAMETERS_END(); + + if (needle->len > haystack->len) { + RETURN_FALSE; + } + + for (i = haystack->len - 1, j = needle->len - 1; j >= 0; i--, j--) + if (haystack->val[i] != needle->val[j]) + RETURN_FALSE; + RETURN_TRUE; +} + +/* {{{ proto boolean str_ends_with_ci(string haystack, string needle) + Performs case insensitive check to determine if haystack ends with needle */ +PHP_FUNCTION(str_ends_with_ci) { + zend_string *haystack, *needle; + int i, j; + + ZEND_PARSE_PARAMETERS_START(2, 2) + Z_PARAM_STR(haystack) + Z_PARAM_STR(needle) + ZEND_PARSE_PARAMETERS_END(); + + if (needle->len > haystack->len) { + RETURN_FALSE; + } + + for (i = haystack->len - 1, j = needle->len - 1; j >= 0; i--, j--) + if (tolower(haystack->val[i]) != tolower(needle->val[j])) + RETURN_FALSE; + RETURN_TRUE; +} + /* {{{ proto string|array|false substr_replace(mixed str, mixed repl, mixed start [, mixed length]) Replaces part of a string with another string */ PHP_FUNCTION(substr_replace) diff --git a/ext/standard/tests/strings/str_ends_with.phpt b/ext/standard/tests/strings/str_ends_with.phpt new file mode 100644 index 0000000000000..0064e9f432d83 --- /dev/null +++ b/ext/standard/tests/strings/str_ends_with.phpt @@ -0,0 +1,18 @@ +--TEST-- +str_ends_with() function - unit tests for str_ends_with() +--FILE-- + +--EXPECT-- +bool(true) +bool(false) +bool(false) +bool(false) diff --git a/ext/standard/tests/strings/str_ends_with_ci.phpt b/ext/standard/tests/strings/str_ends_with_ci.phpt new file mode 100644 index 0000000000000..3d646e190c64e --- /dev/null +++ b/ext/standard/tests/strings/str_ends_with_ci.phpt @@ -0,0 +1,18 @@ +--TEST-- +str_ends_with_ci() function - unit tests for str_ends_with_ci() +--FILE-- + +--EXPECT-- +bool(true) +bool(true) +bool(false) +bool(false) diff --git a/ext/standard/tests/strings/str_starts_with.phpt b/ext/standard/tests/strings/str_starts_with.phpt new file mode 100644 index 0000000000000..90e97bb3254a1 --- /dev/null +++ b/ext/standard/tests/strings/str_starts_with.phpt @@ -0,0 +1,18 @@ +--TEST-- +str_starts_with() function - unit tests for str_starts_with() +--FILE-- + +--EXPECT-- +bool(true) +bool(false) +bool(false) +bool(false) diff --git a/ext/standard/tests/strings/str_starts_with_ci.phpt b/ext/standard/tests/strings/str_starts_with_ci.phpt new file mode 100644 index 0000000000000..7272a98acb5cc --- /dev/null +++ b/ext/standard/tests/strings/str_starts_with_ci.phpt @@ -0,0 +1,18 @@ +--TEST-- +str_starts_with_ci() function - unit tests for str_starts_with_ci() +--FILE-- + +--EXPECT-- +bool(true) +bool(true) +bool(false) +bool(false)