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
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 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
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 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;
Expand Down
64 changes: 64 additions & 0 deletions ext/standard/tests/strings/add_prefix.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
--TEST--
Test add_prefix() function
--FILE--
<?php

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

$testStr = "BeginningMiddleEnd";

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

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

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

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

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

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

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

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

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

?>
--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"
Loading
Loading