Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions Zend/Optimizer/zend_func_infos.h
Original file line number Diff line number Diff line change
Expand Up @@ -481,6 +481,12 @@ static const func_info_t func_infos[] = {
F1("stristr", MAY_BE_STRING|MAY_BE_FALSE),
F1("strstr", MAY_BE_STRING|MAY_BE_FALSE),
F1("strrchr", MAY_BE_STRING|MAY_BE_FALSE),
F1("str_prefix_ensure", MAY_BE_STRING),
F1("str_prefix_remove", MAY_BE_STRING),
F1("str_prefix_replace", MAY_BE_STRING),
F1("str_suffix_ensure", MAY_BE_STRING),
F1("str_suffix_remove", MAY_BE_STRING),
F1("str_suffix_replace", MAY_BE_STRING),
F1("chunk_split", MAY_BE_STRING),
FN("substr_replace", MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_LONG|MAY_BE_ARRAY_KEY_STRING|MAY_BE_ARRAY_OF_STRING),
F1("quotemeta", MAY_BE_STRING),
Expand Down
36 changes: 36 additions & 0 deletions ext/standard/basic_functions.stub.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 str_prefix_ensure(string $subject, string $prefix): string {}

/**
* @compile-time-eval
* @refcount 1
*/
function str_prefix_remove(string $subject, string $prefix): string {}

/**
* @compile-time-eval
* @refcount 1
*/
function str_prefix_replace(string $prefix, string $replace, string $subject): string {}

/**
* @compile-time-eval
* @refcount 1
*/
function str_suffix_ensure(string $subject, string $suffix): string {}

/**
* @compile-time-eval
* @refcount 1
*/
function str_suffix_remove(string $subject, string $suffix): string {}

/**
* @compile-time-eval
* @refcount 1
*/
function str_suffix_replace(string $suffix, string $replace, string $subject): string {}

/**
* @compile-time-eval
* @refcount 1
Expand Down
40 changes: 39 additions & 1 deletion ext/standard/basic_functions_arginfo.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

160 changes: 160 additions & 0 deletions ext/standard/string.c
Original file line number Diff line number Diff line change
Expand Up @@ -1889,6 +1889,166 @@ PHP_FUNCTION(str_ends_with)
}
/* }}} */

/* {{{ Adds prefix to subject string if subject does not already start with prefix */
PHP_FUNCTION(str_prefix_ensure)
{
zend_string *subject, *prefix;

ZEND_PARSE_PARAMETERS_START(2, 2)
Z_PARAM_STR(subject)
Z_PARAM_STR(prefix)
ZEND_PARSE_PARAMETERS_END();

if (ZSTR_LEN(prefix) == 0) {
RETURN_STR_COPY(subject);
}

if (ZSTR_LEN(prefix) <= ZSTR_LEN(subject) &&
memcmp(ZSTR_VAL(subject), ZSTR_VAL(prefix), ZSTR_LEN(prefix)) == 0) {
RETURN_STR_COPY(subject);
}

zend_string *result = zend_string_alloc(ZSTR_LEN(prefix) + ZSTR_LEN(subject), 0);
memcpy(ZSTR_VAL(result), ZSTR_VAL(prefix), ZSTR_LEN(prefix));
memcpy(ZSTR_VAL(result) + ZSTR_LEN(prefix), ZSTR_VAL(subject), ZSTR_LEN(subject));
ZSTR_VAL(result)[ZSTR_LEN(prefix) + ZSTR_LEN(subject)] = '\0';
RETURN_NEW_STR(result);
}
/* }}} */

/* {{{ Removes prefix from subject string if subject starts with prefix */
PHP_FUNCTION(str_prefix_remove)
{
zend_string *subject, *prefix;

ZEND_PARSE_PARAMETERS_START(2, 2)
Z_PARAM_STR(subject)
Z_PARAM_STR(prefix)
ZEND_PARSE_PARAMETERS_END();

if (ZSTR_LEN(prefix) > ZSTR_LEN(subject)) {
RETURN_STR_COPY(subject);
}

if (memcmp(ZSTR_VAL(subject), ZSTR_VAL(prefix), ZSTR_LEN(prefix)) == 0) {
RETURN_STRINGL(ZSTR_VAL(subject) + ZSTR_LEN(prefix), ZSTR_LEN(subject) - ZSTR_LEN(prefix));
}

RETURN_STR_COPY(subject);
}
/* }}} */

/* {{{ Replaces prefix in subject string if subject starts with prefix */
PHP_FUNCTION(str_prefix_replace)
{
zend_string *subject, *prefix, *replace;

ZEND_PARSE_PARAMETERS_START(3, 3)
Z_PARAM_STR(prefix)
Z_PARAM_STR(replace)
Z_PARAM_STR(subject)
ZEND_PARSE_PARAMETERS_END();

if (ZSTR_LEN(prefix) > ZSTR_LEN(subject)) {
RETURN_STR_COPY(subject);
}

if (memcmp(ZSTR_VAL(subject), ZSTR_VAL(prefix), ZSTR_LEN(prefix)) == 0) {
size_t remaining_len = ZSTR_LEN(subject) - 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(subject) + ZSTR_LEN(prefix), remaining_len);
ZSTR_VAL(result)[ZSTR_LEN(replace) + remaining_len] = '\0';
RETURN_NEW_STR(result);
}

RETURN_STR_COPY(subject);
}
/* }}} */

/* {{{ Adds suffix to subject string if subject does not already end with suffix */
PHP_FUNCTION(str_suffix_ensure)
{
zend_string *subject, *suffix;

ZEND_PARSE_PARAMETERS_START(2, 2)
Z_PARAM_STR(subject)
Z_PARAM_STR(suffix)
ZEND_PARSE_PARAMETERS_END();

if (ZSTR_LEN(suffix) == 0) {
RETURN_STR_COPY(subject);
}

if (ZSTR_LEN(suffix) <= ZSTR_LEN(subject) &&
memcmp(
ZSTR_VAL(subject) + ZSTR_LEN(subject) - ZSTR_LEN(suffix),
ZSTR_VAL(suffix), ZSTR_LEN(suffix)) == 0) {
RETURN_STR_COPY(subject);
}

zend_string *result = zend_string_alloc(ZSTR_LEN(subject) + ZSTR_LEN(suffix), 0);
memcpy(ZSTR_VAL(result), ZSTR_VAL(subject), ZSTR_LEN(subject));
memcpy(ZSTR_VAL(result) + ZSTR_LEN(subject), ZSTR_VAL(suffix), ZSTR_LEN(suffix));
ZSTR_VAL(result)[ZSTR_LEN(subject) + ZSTR_LEN(suffix)] = '\0';
RETURN_NEW_STR(result);
}
/* }}} */

/* {{{ Removes suffix from subject string if subject ends with suffix */
PHP_FUNCTION(str_suffix_remove)
{
zend_string *subject, *suffix;

ZEND_PARSE_PARAMETERS_START(2, 2)
Z_PARAM_STR(subject)
Z_PARAM_STR(suffix)
ZEND_PARSE_PARAMETERS_END();

if (ZSTR_LEN(suffix) > ZSTR_LEN(subject)) {
RETURN_STR_COPY(subject);
}

if (memcmp(
ZSTR_VAL(subject) + ZSTR_LEN(subject) - ZSTR_LEN(suffix),
ZSTR_VAL(suffix), ZSTR_LEN(suffix)) == 0) {
RETURN_STRINGL(ZSTR_VAL(subject), ZSTR_LEN(subject) - ZSTR_LEN(suffix));
}

RETURN_STR_COPY(subject);
}
/* }}} */

/* {{{ Replaces suffix in subject string if subject ends with suffix */
PHP_FUNCTION(str_suffix_replace)
{
zend_string *subject, *suffix, *replace;

ZEND_PARSE_PARAMETERS_START(3, 3)
Z_PARAM_STR(suffix)
Z_PARAM_STR(replace)
Z_PARAM_STR(subject)
ZEND_PARSE_PARAMETERS_END();

if (ZSTR_LEN(suffix) > ZSTR_LEN(subject)) {
RETURN_STR_COPY(subject);
}

if (memcmp(
ZSTR_VAL(subject) + ZSTR_LEN(subject) - ZSTR_LEN(suffix),
ZSTR_VAL(suffix), ZSTR_LEN(suffix)) == 0) {
size_t base_len = ZSTR_LEN(subject) - ZSTR_LEN(suffix);
zend_string *result = zend_string_alloc(base_len + ZSTR_LEN(replace), 0);
memcpy(ZSTR_VAL(result), ZSTR_VAL(subject), 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(subject);
}
/* }}} */

static inline void _zend_strpos(zval *return_value, zend_string *haystack, zend_string *needle, zend_long offset)
{
const char *found = NULL;
Expand Down
64 changes: 64 additions & 0 deletions ext/standard/tests/strings/str_prefix_ensure.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
--TEST--
Test str_prefix_ensure() function
--FILE--
<?php

echo "*** Testing str_prefix_ensure() : various strings ***\n";

$testStr = "BeginningMiddleEnd";

// Does not start with prefix - should be added
var_dump(str_prefix_ensure($testStr, "The"));

// Already starts with prefix - should return original
var_dump(str_prefix_ensure($testStr, "Beginning"));

// Empty prefix - should return original
var_dump(str_prefix_ensure($testStr, ""));

// Empty source - should add prefix
var_dump(str_prefix_ensure("", "prefix"));

// Both empty strings
var_dump(str_prefix_ensure("", ""));

// Add prefix to empty string with space
var_dump(str_prefix_ensure("", " "));

// Source shorter than prefix but doesn't start with it
var_dump(str_prefix_ensure("a", "abc"));

// Source equals prefix - should return original
var_dump(str_prefix_ensure("test", "test"));

// Null byte handling
var_dump(str_prefix_ensure($testStr, "\x00"));
var_dump(str_prefix_ensure("\x00", ""));
var_dump(str_prefix_ensure("\x00", "\x00"));
var_dump(str_prefix_ensure("\x00a", "\x00"));
var_dump(str_prefix_ensure("a", "\x00"));
var_dump(str_prefix_ensure("c\x00ab", "\x00ab"));
var_dump(str_prefix_ensure("\x00ab", "c\x00"));
var_dump(str_prefix_ensure("a\x00b", "a\x00d"));
var_dump(str_prefix_ensure("a\x00b", "a\x00z"));

?>
--EXPECTF--
*** Testing str_prefix_ensure() : 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"
Loading
Loading