Commit e2a923265d6 for php.net

commit e2a923265d6080db5950ec4f6947c2bb9d0915e3
Author: Gina Peter Banyard <girgias@php.net>
Date:   Thu Apr 16 19:58:24 2026 +0100

    ext/phar: use zend_string for entry in phar_split_fname() (#21767)

    This saves us to do multiple reallocations from zend_string* to char* back to a zend_string*.

diff --git a/ext/phar/dirstream.c b/ext/phar/dirstream.c
index 51e5d70a6e6..c486fd41abf 100644
--- a/ext/phar/dirstream.c
+++ b/ext/phar/dirstream.c
@@ -350,7 +350,7 @@ int phar_wrapper_mkdir(php_stream_wrapper *wrapper, const char *url_from, int mo
 	php_url *resource = NULL;

 	/* pre-readonly check, we need to know if this is a data phar */
-	if (FAILURE == phar_split_fname(url_from, strlen(url_from), &arch, &arch_len, NULL, NULL, 2, 2)) {
+	if (FAILURE == phar_split_fname(url_from, strlen(url_from), &arch, &arch_len, NULL, 2, 2)) {
 		php_stream_wrapper_log_error(wrapper, options, "phar error: cannot create directory \"%s\", no phar archive specified", url_from);
 		return 0;
 	}
@@ -476,7 +476,7 @@ int phar_wrapper_rmdir(php_stream_wrapper *wrapper, const char *url, int options
 	php_url *resource = NULL;

 	/* pre-readonly check, we need to know if this is a data phar */
-	if (FAILURE == phar_split_fname(url, strlen(url), &arch, &arch_len, NULL, NULL, 2, 2)) {
+	if (FAILURE == phar_split_fname(url, strlen(url), &arch, &arch_len, NULL, 2, 2)) {
 		php_stream_wrapper_log_error(wrapper, options, "phar error: cannot remove directory \"%s\", no phar archive specified, or phar archive does not exist", url);
 		return 0;
 	}
diff --git a/ext/phar/func_interceptors.c b/ext/phar/func_interceptors.c
index a8867ed57cd..e70dcb5e5a1 100644
--- a/ext/phar/func_interceptors.c
+++ b/ext/phar/func_interceptors.c
@@ -48,7 +48,7 @@ PHP_FUNCTION(phar_opendir) /* {{{ */
 			goto skip_phar;
 		}

-		if (SUCCESS == phar_split_fname(ZSTR_VAL(fname), ZSTR_LEN(fname), &arch, &arch_len, NULL, NULL, 2, 0)) {
+		if (SUCCESS == phar_split_fname(ZSTR_VAL(fname), ZSTR_LEN(fname), &arch, &arch_len, NULL, 2, 0)) {
 			php_stream_context *context = NULL;
 			php_stream *stream;
 			char *name;
@@ -94,7 +94,7 @@ static zend_string* phar_get_name_for_relative_paths(zend_string *filename, bool
 		return NULL;
 	}

-	if (FAILURE == phar_split_fname(ZSTR_VAL(fname), ZSTR_LEN(fname), &arch, &arch_len, NULL, NULL, 2, 0)) {
+	if (FAILURE == phar_split_fname(ZSTR_VAL(fname), ZSTR_LEN(fname), &arch, &arch_len, NULL, 2, 0)) {
 		return NULL;
 	}

@@ -496,7 +496,7 @@ static void phar_file_stat(const char *filename, size_t filename_length, int typ
 			phar = PHAR_G(last_phar);
 			goto splitted;
 		}
-		if (SUCCESS == phar_split_fname(ZSTR_VAL(fname), ZSTR_LEN(fname), &arch, &arch_len, NULL, NULL, 2, 0)) {
+		if (SUCCESS == phar_split_fname(ZSTR_VAL(fname), ZSTR_LEN(fname), &arch, &arch_len, NULL, 2, 0)) {
 			/* fopen within phar, if :// is not in the url, then prepend phar://<archive>/ */
 			if (FAILURE == phar_get_archive(&phar, arch, arch_len, NULL, 0, NULL)) {
 				efree(arch);
@@ -737,7 +737,7 @@ PHP_FUNCTION(phar_is_file) /* {{{ */
 			goto skip_phar;
 		}

-		if (SUCCESS == phar_split_fname(ZSTR_VAL(fname), ZSTR_LEN(fname), &arch, &arch_len, NULL, NULL, 2, 0)) {
+		if (SUCCESS == phar_split_fname(ZSTR_VAL(fname), ZSTR_LEN(fname), &arch, &arch_len, NULL, 2, 0)) {
 			phar_archive_data *phar;

 			/* fopen within phar, if :// is not in the url, then prepend phar://<archive>/ */
@@ -795,7 +795,7 @@ PHP_FUNCTION(phar_is_link) /* {{{ */
 			goto skip_phar;
 		}

-		if (SUCCESS == phar_split_fname(ZSTR_VAL(fname), ZSTR_LEN(fname), &arch, &arch_len, NULL, NULL, 2, 0)) {
+		if (SUCCESS == phar_split_fname(ZSTR_VAL(fname), ZSTR_LEN(fname), &arch, &arch_len, NULL, 2, 0)) {
 			phar_archive_data *phar;

 			/* fopen within phar, if :// is not in the url, then prepend phar://<archive>/ */
diff --git a/ext/phar/phar.c b/ext/phar/phar.c
index 02a2f09f952..d597434d5eb 100644
--- a/ext/phar/phar.c
+++ b/ext/phar/phar.c
@@ -2163,7 +2163,7 @@ zend_string* phar_fix_filepath(const char *path, size_t path_length, bool use_cw
  *
  * This is used by phar_parse_url()
  */
-zend_result phar_split_fname(const char *filename, size_t filename_len, char **arch, size_t *arch_len, char **entry, size_t *entry_len, int executable, int for_create) /* {{{ */
+zend_result phar_split_fname(const char *filename, size_t filename_len, char **arch, size_t *arch_len, zend_string **entry, int executable, int for_create) /* {{{ */
 {
 	const char *ext_str;
 #ifdef PHP_WIN32
@@ -2219,13 +2219,9 @@ zend_result phar_split_fname(const char *filename, size_t filename_len, char **a
 			size_t computed_entry_len = filename_len - *arch_len;
 			/* We don't need to unixify the path on Windows,
 			 * as ext_str is derived from filename that was already unixify */
-			zend_string *entry_str = phar_fix_filepath(ext_str+ext_len, computed_entry_len, false);
-			*entry = estrndup(ZSTR_VAL(entry_str), ZSTR_LEN(entry_str));
-			*entry_len = ZSTR_LEN(entry_str);
-			zend_string_release_ex(entry_str, false);
+			*entry = phar_fix_filepath(ext_str+ext_len, computed_entry_len, false);
 		} else {
-			*entry_len = 1;
-			*entry = estrndup("/", 1);
+			*entry = ZSTR_CHAR('/');
 		}
 	}

diff --git a/ext/phar/phar_internal.h b/ext/phar/phar_internal.h
index a2b7e67a2e8..c20e5d42b31 100644
--- a/ext/phar/phar_internal.h
+++ b/ext/phar/phar_internal.h
@@ -474,7 +474,7 @@ ZEND_ATTRIBUTE_NONNULL zend_result phar_get_entry_data(phar_entry_data **ret, ch
 ZEND_ATTRIBUTE_NONNULL_ARGS(1, 4) int phar_flush_ex(phar_archive_data *archive, zend_string *user_stub, bool is_default_stub, char **error);
 ZEND_ATTRIBUTE_NONNULL int phar_flush(phar_archive_data *archive, char **error);
 zend_result phar_detect_phar_fname_ext(const char *filename, size_t filename_len, const char **ext_str, size_t *ext_len, int executable, int for_create, bool is_complete);
-zend_result phar_split_fname(const char *filename, size_t filename_len, char **arch, size_t *arch_len, char **entry, size_t *entry_len, int executable, int for_create);
+zend_result phar_split_fname(const char *filename, size_t filename_len, char **arch, size_t *arch_len, zend_string **entry, int executable, int for_create);

 typedef enum {
 	pcr_use_query,
diff --git a/ext/phar/phar_object.c b/ext/phar/phar_object.c
index f522d52ba6c..ef997184d03 100644
--- a/ext/phar/phar_object.c
+++ b/ext/phar/phar_object.c
@@ -419,7 +419,7 @@ PHP_METHOD(Phar, running)

 	if (
 		zend_string_starts_with_literal_ci(fname, "phar://")
-		&& SUCCESS == phar_split_fname(ZSTR_VAL(fname), ZSTR_LEN(fname), &arch, &arch_len, NULL, NULL, 2, 0)
+		&& SUCCESS == phar_split_fname(ZSTR_VAL(fname), ZSTR_LEN(fname), &arch, &arch_len, NULL, 2, 0)
 	) {
 		if (retphar) {
 			RETVAL_STRINGL(ZSTR_VAL(fname), arch_len + 7);
@@ -444,8 +444,8 @@ PHP_METHOD(Phar, running)
  */
 PHP_METHOD(Phar, mount)
 {
-	char *fname, *arch = NULL, *entry = NULL, *path, *actual;
-	size_t fname_len, arch_len, entry_len;
+	char *fname, *arch = NULL, *path, *actual;
+	size_t fname_len, arch_len;
 	size_t path_len, actual_len;
 	phar_archive_data *pphar;
 #ifdef PHP_WIN32
@@ -476,9 +476,8 @@ PHP_METHOD(Phar, mount)
 	}
 #endif

-	if (fname_len > 7 && !memcmp(fname, "phar://", 7) && SUCCESS == phar_split_fname(fname, fname_len, &arch, &arch_len, NULL, NULL, 2, 0)) {
-		entry = NULL;
-
+	zend_string *entry = NULL;
+	if (fname_len > 7 && !memcmp(fname, "phar://", 7) && SUCCESS == phar_split_fname(fname, fname_len, &arch, &arch_len, NULL, 2, 0)) {
 		if (path_len > 7 && !memcmp(path, "phar://", 7)) {
 			zend_throw_exception_ex(phar_ce_PharException, 0, "Can only mount internal paths within a phar archive, use a relative path instead of \"%s\"", path);
 			efree(arch);
@@ -505,8 +504,8 @@ PHP_METHOD(Phar, mount)
 			zend_throw_exception_ex(phar_ce_PharException, 0, "Mounting of %s to %s within phar %s failed", path, actual, arch);
 		}

-		if (entry && path == entry) {
-			efree(entry);
+		if (entry && path == ZSTR_VAL(entry)) {
+			zend_string_release_ex(entry, false);
 		}

 		if (arch) {
@@ -522,9 +521,9 @@ PHP_METHOD(Phar, mount)
 		}

 		goto carry_on;
-	} else if (SUCCESS == phar_split_fname(path, path_len, &arch, &arch_len, &entry, &entry_len, 2, 0)) {
-		path = entry;
-		path_len = entry_len;
+	} else if (SUCCESS == phar_split_fname(path, path_len, &arch, &arch_len, &entry, 2, 0)) {
+		path = ZSTR_VAL(entry);
+		path_len = ZSTR_LEN(entry);
 		goto carry_on2;
 	}

@@ -1078,9 +1077,9 @@ static const spl_other_handler phar_spl_foreign_handler = {
  */
 PHP_METHOD(Phar, __construct)
 {
-	char *fname, *alias = NULL, *error, *arch = NULL, *entry = NULL, *save_fname;
+	char *fname, *alias = NULL, *error, *arch = NULL, *save_fname;
 	size_t fname_len, alias_len = 0;
-	size_t arch_len, entry_len;
+	size_t arch_len;
 	bool is_data;
 	zend_long flags = SPL_FILE_DIR_SKIPDOTS|SPL_FILE_DIR_UNIXPATHS;
 	zend_long format = 0;
@@ -1108,7 +1107,8 @@ PHP_METHOD(Phar, __construct)
 	}

 	save_fname = fname;
-	if (SUCCESS == phar_split_fname(fname, fname_len, &arch, &arch_len, &entry, &entry_len, !is_data, 2)) {
+	zend_string *entry = NULL;
+	if (SUCCESS == phar_split_fname(fname, fname_len, &arch, &arch_len, &entry, !is_data, 2)) {
 		/* use arch (the basename for the archive) for fname instead of fname */
 		/* this allows support for RecursiveDirectoryIterator of subdirectories */
 #ifdef PHP_WIN32
@@ -1133,7 +1133,7 @@ PHP_METHOD(Phar, __construct)
 		}

 		if (entry) {
-			efree(entry);
+			zend_string_release_ex(entry, false);
 		}

 		if (error) {
@@ -1166,7 +1166,7 @@ PHP_METHOD(Phar, __construct)
 			zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0,
 				"Phar class can only be used for executable tar and zip archives");
 		}
-		efree(entry);
+		zend_string_release_ex(entry, false);
 		RETURN_THROWS();
 	}

@@ -1184,9 +1184,9 @@ PHP_METHOD(Phar, __construct)
 		file_name_for_recursive_director_iterator_constructor = zend_string_concat3(
 			ZEND_STRL("phar://"),
 			phar_data->fname, phar_data->fname_len,
-			entry, entry_len
+			ZSTR_VAL(entry), ZSTR_LEN(entry)
 		);
-		efree(entry);
+		zend_string_release_ex(entry, false);
 	} else {
 		file_name_for_recursive_director_iterator_constructor = zend_string_concat2(
 			ZEND_STRL("phar://"),
@@ -1289,7 +1289,7 @@ PHP_METHOD(Phar, unlinkArchive)
 	if (
 		zend_file_name
 		&& zend_string_starts_with_literal_ci(zend_file_name, "phar://")
-		&& SUCCESS == phar_split_fname(ZSTR_VAL(zend_file_name), ZSTR_LEN(zend_file_name), &arch, &arch_len, NULL, NULL, 2, 0)
+		&& SUCCESS == phar_split_fname(ZSTR_VAL(zend_file_name), ZSTR_LEN(zend_file_name), &arch, &arch_len, NULL, 2, 0)
 	) {
 		if (arch_len == fname_len && !memcmp(arch, fname, arch_len)) {
 			zend_throw_exception_ex(phar_ce_PharException, 0, "phar archive \"%s\" cannot be unlinked from within itself", fname);
@@ -4392,11 +4392,10 @@ PHP_METHOD(Phar, extractTo)
 /* {{{ Construct a Phar entry object */
 PHP_METHOD(PharFileInfo, __construct)
 {
-	char *fname, *arch, *entry, *error;
+	char *fname, *arch, *error;
 	size_t fname_len;
-	size_t arch_len, entry_len;
+	size_t arch_len;
 	phar_entry_object *entry_obj;
-	phar_entry_info *entry_info;
 	phar_archive_data *phar_data;
 	zval arg1;

@@ -4411,7 +4410,8 @@ PHP_METHOD(PharFileInfo, __construct)
 		RETURN_THROWS();
 	}

-	if (fname_len < 7 || memcmp(fname, "phar://", 7) || phar_split_fname(fname, fname_len, &arch, &arch_len, &entry, &entry_len, 2, 0) == FAILURE) {
+	zend_string *entry = NULL;
+	if (fname_len < 7 || memcmp(fname, "phar://", 7) || phar_split_fname(fname, fname_len, &arch, &arch_len, &entry, 2, 0) == FAILURE) {
 		zend_throw_exception_ex(spl_ce_RuntimeException, 0,
 			"'%s' is not a valid phar archive URL (must have at least phar://filename.phar)", fname);
 		RETURN_THROWS();
@@ -4431,16 +4431,17 @@ PHP_METHOD(PharFileInfo, __construct)
 		RETURN_THROWS();
 	}

-	if ((entry_info = phar_get_entry_info_dir(phar_data, entry, entry_len, 1, &error, true)) == NULL) {
+	phar_entry_info *entry_info = phar_get_entry_info_dir(phar_data, ZSTR_VAL(entry), ZSTR_LEN(entry), 1, &error, true);
+	if (UNEXPECTED(!entry_info)) {
 		zend_throw_exception_ex(spl_ce_RuntimeException, 0,
-			"Cannot access phar file entry '%s' in archive '%s'%s%s", entry, arch, error ? ", " : "", error ? error : "");
+		"Cannot access phar file entry '%s' in archive '%s'%s%s", ZSTR_VAL(entry), arch, error ? ", " : "", error ? error : "");
+		zend_string_release_ex(entry, false);
 		efree(arch);
-		efree(entry);
 		RETURN_THROWS();
 	}

+	zend_string_release_ex(entry, false);
 	efree(arch);
-	efree(entry);

 	entry_obj->entry = entry_info;
 	if (!entry_info->is_persistent && !entry_info->is_temp_dir) {
diff --git a/ext/phar/stream.c b/ext/phar/stream.c
index 6694fec9479..4b49703b4ab 100644
--- a/ext/phar/stream.c
+++ b/ext/phar/stream.c
@@ -58,8 +58,8 @@ const php_stream_wrapper php_stream_phar_wrapper = {
 php_url* phar_parse_url(php_stream_wrapper *wrapper, const char *filename, const char *mode, int options) /* {{{ */
 {
 	php_url *resource;
-	char *arch = NULL, *entry = NULL, *error;
-	size_t arch_len, entry_len;
+	char *arch = NULL, *error;
+	size_t arch_len;

 	if (strncasecmp(filename, "phar://", 7)) {
 		return NULL;
@@ -70,7 +70,8 @@ php_url* phar_parse_url(php_stream_wrapper *wrapper, const char *filename, const
 		}
 		return NULL;
 	}
-	if (phar_split_fname(filename, strlen(filename), &arch, &arch_len, &entry, &entry_len, 2, (mode[0] == 'w' ? 2 : 0)) == FAILURE) {
+	zend_string *entry = NULL;
+	if (phar_split_fname(filename, strlen(filename), &arch, &arch_len, &entry, 2, (mode[0] == 'w' ? 2 : 0)) == FAILURE) {
 		if (!(options & PHP_STREAM_URL_STAT_QUIET)) {
 			if (arch && !entry) {
 				php_stream_wrapper_log_error(wrapper, options, "phar error: no directory in \"%s\", must have at least phar://%s/ for root directory (always use full path to a new phar)", filename, arch);
@@ -85,8 +86,7 @@ php_url* phar_parse_url(php_stream_wrapper *wrapper, const char *filename, const
 	resource->scheme = ZSTR_INIT_LITERAL("phar", 0);
 	resource->host = zend_string_init(arch, arch_len, 0);
 	efree(arch);
-	resource->path = zend_string_init(entry, entry_len, 0);
-	efree(entry);
+	resource->path = entry;

 #ifdef MBO_0
 		if (resource) {
diff --git a/ext/phar/util.c b/ext/phar/util.c
index aad6e5f7f08..1e9fedbcbb2 100644
--- a/ext/phar/util.c
+++ b/ext/phar/util.c
@@ -300,7 +300,7 @@ zend_string *phar_find_in_include_path(zend_string *filename, phar_archive_data
 		goto splitted;
 	}

-	if (!is_file_a_phar_wrapper || SUCCESS != phar_split_fname(ZSTR_VAL(fname), ZSTR_LEN(fname), &arch, &arch_len, NULL, NULL, 1, 0)) {
+	if (!is_file_a_phar_wrapper || SUCCESS != phar_split_fname(ZSTR_VAL(fname), ZSTR_LEN(fname), &arch, &arch_len, NULL, 1, 0)) {
 		return NULL;
 	}

@@ -344,7 +344,7 @@ zend_string *phar_find_in_include_path(zend_string *filename, phar_archive_data

 	if (ret && zend_string_starts_with_literal_ci(ret, "phar://")) {
 		/* found phar:// */
-		if (SUCCESS != phar_split_fname(ZSTR_VAL(ret), ZSTR_LEN(ret), &arch, &arch_len, NULL, NULL, 1, 0)) {
+		if (SUCCESS != phar_split_fname(ZSTR_VAL(ret), ZSTR_LEN(ret), &arch, &arch_len, NULL, 1, 0)) {
 			return ret;
 		}