Commit 8d28c0562b7 for php.net

commit 8d28c0562b70f6b0512ad7c05780cc51a43709bc
Author: Gina Peter Banyard <girgias@php.net>
Date:   Fri Apr 17 18:53:08 2026 +0100

    ext/phar: refactor phar_split_fname() to return zend_string* rather than out params (#21777)

diff --git a/ext/phar/dirstream.c b/ext/phar/dirstream.c
index c486fd41abf..64cace88992 100644
--- a/ext/phar/dirstream.c
+++ b/ext/phar/dirstream.c
@@ -345,21 +345,21 @@ int phar_wrapper_mkdir(php_stream_wrapper *wrapper, const char *url_from, int mo
 {
 	phar_entry_info entry, *e;
 	phar_archive_data *phar = NULL;
-	char *error, *arch;
-	size_t arch_len;
+	char *error;
 	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, 2, 2)) {
+	zend_string *arch = phar_split_fname(url_from, strlen(url_from), NULL, 2, 2);
+	if (!arch) {
 		php_stream_wrapper_log_error(wrapper, options, "phar error: cannot create directory \"%s\", no phar archive specified", url_from);
 		return 0;
 	}

-	if (FAILURE == phar_get_archive(&phar, arch, arch_len, NULL, 0, NULL)) {
+	if (FAILURE == phar_get_archive(&phar, ZSTR_VAL(arch), ZSTR_LEN(arch), NULL, 0, NULL)) {
 		phar = NULL;
 	}

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

 	if (PHAR_G(readonly) && (!phar || !phar->is_data)) {
 		php_stream_wrapper_log_error(wrapper, options, "phar error: cannot create directory \"%s\", write operations disabled", url_from);
@@ -471,21 +471,21 @@ int phar_wrapper_rmdir(php_stream_wrapper *wrapper, const char *url, int options
 {
 	phar_entry_info *entry;
 	phar_archive_data *phar = NULL;
-	char *error, *arch;
-	size_t arch_len;
+	char *error;
 	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, 2, 2)) {
+	zend_string *arch = phar_split_fname(url, strlen(url), NULL, 2, 2);
+	if (!arch) {
 		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;
 	}

-	if (FAILURE == phar_get_archive(&phar, arch, arch_len, NULL, 0, NULL)) {
+	if (FAILURE == phar_get_archive(&phar, ZSTR_VAL(arch), ZSTR_LEN(arch), NULL, 0, NULL)) {
 		phar = NULL;
 	}

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

 	if (PHAR_G(readonly) && (!phar || !phar->is_data)) {
 		php_stream_wrapper_log_error(wrapper, options, "phar error: cannot rmdir directory \"%s\", write operations disabled", url);
diff --git a/ext/phar/func_interceptors.c b/ext/phar/func_interceptors.c
index 0e7d2df0db6..20ce0e8d725 100644
--- a/ext/phar/func_interceptors.c
+++ b/ext/phar/func_interceptors.c
@@ -38,8 +38,6 @@ PHP_FUNCTION(phar_opendir) /* {{{ */
 	}

 	if (!IS_ABSOLUTE_PATH(filename, filename_len) && !strstr(filename, "://")) {
-		char *arch;
-		size_t arch_len;
 		zend_string *fname = zend_get_executed_filename_ex();

 		/* we are checking for existence of a file within the relative path.  Chances are good that this is
@@ -48,7 +46,8 @@ PHP_FUNCTION(phar_opendir) /* {{{ */
 			goto skip_phar;
 		}

-		if (SUCCESS == phar_split_fname(ZSTR_VAL(fname), ZSTR_LEN(fname), &arch, &arch_len, NULL, 2, 0)) {
+		zend_string *arch = phar_split_fname(ZSTR_VAL(fname), ZSTR_LEN(fname), NULL, 2, 0);
+		if (arch) {
 			php_stream_context *context = NULL;
 			php_stream *stream;
 			char *name;
@@ -58,12 +57,12 @@ PHP_FUNCTION(phar_opendir) /* {{{ */
 			zend_string *entry = phar_fix_filepath(filename, filename_len, true);

 			if (ZSTR_VAL(entry)[0] == '/') {
-				spprintf(&name, 4096, "phar://%s%s", arch, ZSTR_VAL(entry));
+				spprintf(&name, 4096, "phar://%s%s", ZSTR_VAL(arch), ZSTR_VAL(entry));
 			} else {
-				spprintf(&name, 4096, "phar://%s/%s", arch, ZSTR_VAL(entry));
+				spprintf(&name, 4096, "phar://%s/%s", ZSTR_VAL(arch), ZSTR_VAL(entry));
 			}
 			zend_string_release_ex(entry, false);
-			efree(arch);
+			zend_string_release_ex(arch, false);
 			if (zcontext) {
 				context = php_stream_context_from_zval(zcontext, 0);
 			}
@@ -84,8 +83,6 @@ PHP_FUNCTION(phar_opendir) /* {{{ */

 static zend_string* phar_get_name_for_relative_paths(zend_string *filename, bool using_include_path)
 {
-	char *arch;
-	size_t arch_len;
 	zend_string *fname = zend_get_executed_filename_ex();

 	/* we are checking for existence of a file within the relative path.  Chances are good that this is
@@ -94,15 +91,16 @@ 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, 2, 0)) {
+	zend_string *arch = phar_split_fname(ZSTR_VAL(fname), ZSTR_LEN(fname), NULL, 2, 0);
+	if (!arch) {
 		return NULL;
 	}

 	/* fopen within phar, if :// is not in the url, then prepend phar://<archive>/ */
 	/* retrieving a file defaults to within the current directory, so use this if possible */
 	phar_archive_data *phar;
-	if (FAILURE == phar_get_archive(&phar, arch, arch_len, NULL, 0, NULL)) {
-		efree(arch);
+	if (FAILURE == phar_get_archive(&phar, ZSTR_VAL(arch), ZSTR_LEN(arch), NULL, 0, NULL)) {
+		zend_string_release_ex(arch, false);
 		return NULL;
 	}

@@ -110,7 +108,7 @@ static zend_string* phar_get_name_for_relative_paths(zend_string *filename, bool
 	if (using_include_path) {
 		if (!(name = phar_find_in_include_path(filename, NULL))) {
 			/* this file is not in the phar, use the original path */
-			efree(arch);
+			zend_string_release_ex(arch, false);
 			return NULL;
 		}
 	} else {
@@ -124,24 +122,24 @@ static zend_string* phar_get_name_for_relative_paths(zend_string *filename, bool
 		/* this file is not in the phar, use the original path */
 		if (!is_in_phar) {
 			zend_string_release_ex(entry, false);
-			efree(arch);
+			zend_string_release_ex(arch, false);
 			return NULL;
 		}
 		/* auto-convert to phar:// */
 		if (ZSTR_VAL(entry)[0] == '/') {
-			ZEND_ASSERT(strlen("phar://") + arch_len + ZSTR_LEN(entry) < 4096);
+			ZEND_ASSERT(strlen("phar://") + ZSTR_LEN(arch) + ZSTR_LEN(entry) < 4096);
 			name = zend_string_concat3(
 				"phar://", strlen("phar://"),
-				arch, arch_len,
+				ZSTR_VAL(arch), ZSTR_LEN(arch),
 				ZSTR_VAL(entry), ZSTR_LEN(entry)
 			);
 		} else {
-			name = strpprintf(4096, "phar://%s/%s", arch, ZSTR_VAL(entry));
+			name = strpprintf(4096, "phar://%s/%s", ZSTR_VAL(arch), ZSTR_VAL(entry));
 		}
 		zend_string_release_ex(entry, false);
 	}

-	efree(arch);
+	zend_string_release_ex(arch, false);
 	return name;
 }

@@ -492,12 +490,12 @@ static void phar_file_stat(const char *filename, size_t filename_length, int typ
 			phar = PHAR_G(last_phar);
 			goto splitted;
 		}
-		char *arch;
-		size_t arch_len;
-		if (SUCCESS == phar_split_fname(ZSTR_VAL(fname), ZSTR_LEN(fname), &arch, &arch_len, NULL, 2, 0)) {
+
+		zend_string *arch = phar_split_fname(ZSTR_VAL(fname), ZSTR_LEN(fname), NULL, 2, 0);
+		if (arch) {
 			/* fopen within phar, if :// is not in the url, then prepend phar://<archive>/ */
-			zend_result has_archive = phar_get_archive(&phar, arch, arch_len, NULL, 0, NULL);
-			efree(arch);
+			zend_result has_archive = phar_get_archive(&phar, ZSTR_VAL(arch), ZSTR_LEN(arch), NULL, 0, NULL);
+			zend_string_release_ex(arch, false);
 			if (FAILURE == has_archive) {
 				goto skip_phar;
 			}
@@ -721,8 +719,6 @@ PHP_FUNCTION(phar_is_file) /* {{{ */
 		goto skip_phar;
 	}
 	if (!IS_ABSOLUTE_PATH(filename, filename_len) && !strstr(filename, "://")) {
-		char *arch;
-		size_t arch_len;
 		zend_string *fname = zend_get_executed_filename_ex();

 		/* we are checking for existence of a file within the relative path.  Chances are good that this is
@@ -731,12 +727,15 @@ PHP_FUNCTION(phar_is_file) /* {{{ */
 			goto skip_phar;
 		}

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

 			/* fopen within phar, if :// is not in the url, then prepend phar://<archive>/ */
 			/* retrieving a file within the current directory, so use this if possible */
-			if (SUCCESS == phar_get_archive(&phar, arch, arch_len, NULL, 0, NULL)) {
+			zend_result has_archive = phar_get_archive(&phar, ZSTR_VAL(arch), ZSTR_LEN(arch), NULL, 0, NULL);
+			zend_string_release_ex(arch, false);
+			if (has_archive == SUCCESS) {
 				phar_entry_info *etemp;

 				zend_string *entry = phar_fix_filepath(filename, filename_len, true);
@@ -747,12 +746,10 @@ PHP_FUNCTION(phar_is_file) /* {{{ */
 				}
 				zend_string_release_ex(entry, false);
 				if (etemp) {
-					efree(arch);
 					RETURN_BOOL(!etemp->is_dir);
 				}
 				/* this file is not in the current directory, use the original path */
 			}
-			efree(arch);
 			RETURN_FALSE;
 		}
 	}
@@ -779,8 +776,6 @@ PHP_FUNCTION(phar_is_link) /* {{{ */
 		goto skip_phar;
 	}
 	if (!IS_ABSOLUTE_PATH(filename, filename_len) && !strstr(filename, "://")) {
-		char *arch;
-		size_t arch_len;
 		zend_string *fname = zend_get_executed_filename_ex();

 		/* we are checking for existence of a file within the relative path.  Chances are good that this is
@@ -789,12 +784,15 @@ PHP_FUNCTION(phar_is_link) /* {{{ */
 			goto skip_phar;
 		}

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

 			/* fopen within phar, if :// is not in the url, then prepend phar://<archive>/ */
 			/* retrieving a file within the current directory, so use this if possible */
-			if (SUCCESS == phar_get_archive(&phar, arch, arch_len, NULL, 0, NULL)) {
+			zend_result has_archive = phar_get_archive(&phar, ZSTR_VAL(arch), ZSTR_LEN(arch), NULL, 0, NULL);
+			zend_string_release_ex(arch, false);
+			if (has_archive == SUCCESS) {
 				phar_entry_info *etemp;

 				zend_string *entry = phar_fix_filepath(filename, filename_len, true);
@@ -805,11 +803,9 @@ PHP_FUNCTION(phar_is_link) /* {{{ */
 				}
 				zend_string_release_ex(entry, false);
 				if (etemp) {
-					efree(arch);
 					RETURN_BOOL(etemp->link);
 				}
 			}
-			efree(arch);
 			RETURN_FALSE;
 		}
 	}
diff --git a/ext/phar/phar.c b/ext/phar/phar.c
index 5fb6c864be7..3008316f627 100644
--- a/ext/phar/phar.c
+++ b/ext/phar/phar.c
@@ -2164,7 +2164,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, zend_string **entry, int executable, int for_create) /* {{{ */
+zend_string* phar_split_fname_ex(const char *filename, size_t filename_len, zend_string **entry, int executable, int for_create, const char **error) /* {{{ */
 {
 	const char *ext_str;
 #ifdef PHP_WIN32
@@ -2172,8 +2172,11 @@ zend_result phar_split_fname(const char *filename, size_t filename_len, char **a
 #endif
 	size_t ext_len;

+	if (error) {
+		*error = NULL;
+	}
 	if (zend_char_has_nul_byte(filename, filename_len)) {
-		return FAILURE;
+		return NULL;
 	}

 	if (!strncasecmp(filename, "phar://", 7)) {
@@ -2191,12 +2194,12 @@ zend_result phar_split_fname(const char *filename, size_t filename_len, char **a
 #endif
 	if (phar_detect_phar_fname_ext(filename, filename_len, &ext_str, &ext_len, executable, for_create, false) == FAILURE) {
 		if (ext_len != -1) {
-			if (!ext_str) {
+			if (!ext_str && error) {
 				/* no / detected, restore arch for error message */
 #ifdef PHP_WIN32
-				*arch = save;
+				*error = save;
 #else
-				*arch = (char*)filename;
+				*error = filename;
 #endif
 			}

@@ -2205,19 +2208,19 @@ zend_result phar_split_fname(const char *filename, size_t filename_len, char **a
 				efree((char *)filename);
 			}
 #endif
-			return FAILURE;
+			return NULL;
 		}

 		ext_len = 0;
 		/* no extension detected - instead we are dealing with an alias */
 	}

-	*arch_len = ext_str - filename + ext_len;
-	*arch = estrndup(filename, *arch_len);
+	size_t arch_len = ext_str - filename + ext_len;
+	zend_string *arch = zend_string_init(filename, arch_len, false);

 	if (entry) {
 		if (ext_str[ext_len]) {
-			size_t computed_entry_len = filename_len - *arch_len;
+			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 */
 			*entry = phar_fix_filepath(ext_str+ext_len, computed_entry_len, false);
@@ -2232,10 +2235,14 @@ zend_result phar_split_fname(const char *filename, size_t filename_len, char **a
 	}
 #endif

-	return SUCCESS;
+	return arch;
 }
 /* }}} */

+zend_string* phar_split_fname(const char *filename, size_t filename_len, zend_string **entry, int executable, int for_create) {
+	return phar_split_fname_ex(filename, filename_len, entry, executable, for_create, NULL);
+}
+
 /**
  * Invoked when a user calls Phar::mapPhar() from within an executing .phar
  * to set up its manifest directly
diff --git a/ext/phar/phar_internal.h b/ext/phar/phar_internal.h
index a60c5ed5d43..34f221f43e0 100644
--- a/ext/phar/phar_internal.h
+++ b/ext/phar/phar_internal.h
@@ -474,7 +474,8 @@ ZEND_ATTRIBUTE_NONNULL zend_result phar_get_entry_data(phar_entry_data **ret, co
 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, zend_string **entry, int executable, int for_create);
+zend_string* phar_split_fname_ex(const char *filename, size_t filename_len, zend_string **entry, int executable, int for_create, const char **error);
+zend_string* phar_split_fname(const char *filename, size_t filename_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 f97240010d4..07058eefc46 100644
--- a/ext/phar/phar_object.c
+++ b/ext/phar/phar_object.c
@@ -403,33 +403,27 @@ static void phar_postprocess_ru_web(char *fname, size_t fname_len, char *entry,
  */
 PHP_METHOD(Phar, running)
 {
-	zend_string *fname;
-	char *arch;
-	size_t arch_len;
 	bool retphar = true;

 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "|b", &retphar) == FAILURE) {
 		RETURN_THROWS();
 	}

-	fname = zend_get_executed_filename_ex();
+	const zend_string *fname = zend_get_executed_filename_ex();
 	if (!fname) {
 		RETURN_EMPTY_STRING();
 	}

-	if (
-		zend_string_starts_with_literal_ci(fname, "phar://")
-		&& 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);
-			efree(arch);
-			return;
-		} else {
-			// TODO: avoid reallocation ???
-			RETVAL_STRINGL(arch, arch_len);
-			efree(arch);
-			return;
+	if (zend_string_starts_with_literal_ci(fname, "phar://")) {
+		zend_string *arch = phar_split_fname(ZSTR_VAL(fname), ZSTR_LEN(fname), NULL, 2, 0);
+		if (arch) {
+			if (retphar) {
+				RETVAL_STRINGL(ZSTR_VAL(fname), ZSTR_LEN(arch) + 7);
+				zend_string_release_ex(arch, false);
+				return;
+			} else {
+				RETURN_STR(arch);
+			}
 		}
 	}

@@ -444,8 +438,8 @@ PHP_METHOD(Phar, running)
  */
 PHP_METHOD(Phar, mount)
 {
-	char *fname, *arch = NULL, *path, *actual;
-	size_t fname_len, arch_len;
+	char *fname, *path, *actual;
+	size_t fname_len;
 	size_t path_len, actual_len;
 	phar_archive_data *pphar;
 #ifdef PHP_WIN32
@@ -477,31 +471,29 @@ PHP_METHOD(Phar, mount)
 #endif

 	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)) {
+	zend_string *arch = NULL;
+	if (fname_len > 7 && !memcmp(fname, "phar://", 7) && (arch = phar_split_fname(fname, fname_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);
+			zend_string_release_ex(arch, false);
 			goto finish;
 		}
 carry_on2:
-		if (NULL == (pphar = zend_hash_str_find_ptr(&(PHAR_G(phar_fname_map)), arch, arch_len))) {
-			if (PHAR_G(manifest_cached) && NULL != (pphar = zend_hash_str_find_ptr(&cached_phars, arch, arch_len))) {
+		if (NULL == (pphar = zend_hash_find_ptr(&(PHAR_G(phar_fname_map)), arch))) {
+			if (PHAR_G(manifest_cached) && NULL != (pphar = zend_hash_find_ptr(&cached_phars, arch))) {
 				if (SUCCESS == phar_copy_on_write(&pphar)) {
 					goto carry_on;
 				}
 			}

-			zend_throw_exception_ex(phar_ce_PharException, 0, "%s is not a phar archive, cannot mount", arch);
-
-			if (arch) {
-				efree(arch);
-			}
+			zend_throw_exception_ex(phar_ce_PharException, 0, "%s is not a phar archive, cannot mount", ZSTR_VAL(arch));
+			zend_string_release_ex(arch, false);

 			goto finish;
 		}
 carry_on:
 		if (SUCCESS != phar_mount_entry(pphar, actual, actual_len, path, path_len)) {
-			zend_throw_exception_ex(phar_ce_PharException, 0, "Mounting of %s to %s within phar %s failed", path, actual, arch);
+			zend_throw_exception_ex(phar_ce_PharException, 0, "Mounting of %s to %s within phar %s failed", path, actual, ZSTR_VAL(arch));
 		}

 		if (entry && path == ZSTR_VAL(entry)) {
@@ -509,7 +501,7 @@ PHP_METHOD(Phar, mount)
 		}

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

 		goto finish;
@@ -521,7 +513,7 @@ PHP_METHOD(Phar, mount)
 		}

 		goto carry_on;
-	} else if (SUCCESS == phar_split_fname(path, path_len, &arch, &arch_len, &entry, 2, 0)) {
+	} else if ((arch = phar_split_fname(path, path_len, &entry, 2, 0))) {
 		path = ZSTR_VAL(entry);
 		path_len = ZSTR_LEN(entry);
 		goto carry_on2;
@@ -1077,9 +1069,8 @@ static const spl_other_handler phar_spl_foreign_handler = {
  */
 PHP_METHOD(Phar, __construct)
 {
-	char *fname, *alias = NULL, *error, *arch = NULL, *save_fname;
+	char *fname, *alias = NULL, *error, *save_fname;
 	size_t fname_len, alias_len = 0;
-	size_t arch_len;
 	bool is_data;
 	zend_long flags = SPL_FILE_DIR_SKIPDOTS|SPL_FILE_DIR_UNIXPATHS;
 	zend_long format = 0;
@@ -1108,27 +1099,19 @@ PHP_METHOD(Phar, __construct)

 	save_fname = fname;
 	zend_string *entry = NULL;
-	if (SUCCESS == phar_split_fname(fname, fname_len, &arch, &arch_len, &entry, !is_data, 2)) {
+	/* phar_split_fname() will unixify the path */
+	zend_string *arch = phar_split_fname(fname, fname_len, &entry, !is_data, 2);
+	if (arch) {
 		/* use arch (the basename for the archive) for fname instead of fname */
 		/* this allows support for RecursiveDirectoryIterator of subdirectories */
-#ifdef PHP_WIN32
-		phar_unixify_path_separators(arch, arch_len);
-#endif
-		fname = arch;
-		fname_len = arch_len;
-#ifdef PHP_WIN32
-	} else {
-		arch = estrndup(fname, fname_len);
-		arch_len = fname_len;
-		fname = arch;
-		phar_unixify_path_separators(arch, arch_len);
-#endif
+		fname = ZSTR_VAL(arch);
+		fname_len = ZSTR_LEN(arch);
 	}

 	if (phar_open_or_create_filename(fname, fname_len, alias, alias_len, is_data, REPORT_ERRORS, &phar_data, &error) == FAILURE) {

-		if (fname == arch && fname != save_fname) {
-			efree(arch);
+		if (arch && fname == ZSTR_VAL(arch) && fname != save_fname) {
+			zend_string_release_ex(arch, false);
 			fname = save_fname;
 		}

@@ -1153,8 +1136,8 @@ PHP_METHOD(Phar, __construct)
 		phar_data->is_tar = false;
 	}

-	if (fname == arch) {
-		efree(arch);
+	if (arch && fname == ZSTR_VAL(arch)) {
+		zend_string_release_ex(arch, false);
 		fname = save_fname;
 	}

@@ -1260,9 +1243,8 @@ PHP_METHOD(Phar, getSupportedCompression)
 /* {{{ Completely remove a phar archive from memory and disk */
 PHP_METHOD(Phar, unlinkArchive)
 {
-	char *fname, *error, *arch;
+	char *fname, *error;
 	size_t fname_len;
-	size_t arch_len;
 	phar_archive_data *phar;

 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "p", &fname, &fname_len) == FAILURE) {
@@ -1284,19 +1266,17 @@ PHP_METHOD(Phar, unlinkArchive)
 		RETURN_THROWS();
 	}

-	zend_string *zend_file_name = zend_get_executed_filename_ex();
-
-	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, 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);
-			efree(arch);
-			RETURN_THROWS();
+	const zend_string *zend_file_name = zend_get_executed_filename_ex();
+	if (zend_file_name && zend_string_starts_with_literal_ci(zend_file_name, "phar://")) {
+		zend_string *arch = phar_split_fname(ZSTR_VAL(zend_file_name), ZSTR_LEN(zend_file_name), NULL, 2, 0);
+		if (arch) {
+			if (ZSTR_LEN(arch) == fname_len && !memcmp(ZSTR_VAL(arch), fname, ZSTR_LEN(arch))) {
+				zend_string_release_ex(arch, false);
+				zend_throw_exception_ex(phar_ce_PharException, 0, "phar archive \"%s\" cannot be unlinked from within itself", fname);
+				RETURN_THROWS();
+			}
+			zend_string_release_ex(arch, false);
 		}
-		efree(arch);
 	}

 	if (phar->is_persistent) {
@@ -4395,9 +4375,8 @@ PHP_METHOD(Phar, extractTo)
 /* {{{ Construct a Phar entry object */
 PHP_METHOD(PharFileInfo, __construct)
 {
-	char *fname, *arch, *error;
+	char *fname, *error;
 	size_t fname_len;
-	size_t arch_len;
 	phar_entry_object *entry_obj;
 	phar_archive_data *phar_data;
 	zval arg1;
@@ -4413,15 +4392,22 @@ PHP_METHOD(PharFileInfo, __construct)
 		RETURN_THROWS();
 	}

+	if (fname_len < 7 || memcmp(fname, "phar://", 7)) {
+		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();
+	}
+
 	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_string *arch = phar_split_fname(fname, fname_len, &entry, 2, 0);
+	if (!arch) {
 		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();
 	}

-	if (phar_open_from_filename(arch, arch_len, NULL, 0, REPORT_ERRORS, &phar_data, &error) == FAILURE) {
-		efree(arch);
+	if (phar_open_from_filename(ZSTR_VAL(arch), ZSTR_LEN(arch), NULL, 0, REPORT_ERRORS, &phar_data, &error) == FAILURE) {
+		zend_string_release_ex(arch, false);
 		efree(entry);
 		if (error) {
 			zend_throw_exception_ex(spl_ce_RuntimeException, 0,
@@ -4437,14 +4423,15 @@ PHP_METHOD(PharFileInfo, __construct)
 	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", ZSTR_VAL(entry), arch, error ? ", " : "", error ? error : "");
+			"Cannot access phar file entry '%s' in archive '%s'%s%s",
+			ZSTR_VAL(entry), ZSTR_VAL(arch), error ? ", " : "", error ? error : "");
 		zend_string_release_ex(entry, false);
-		efree(arch);
+		zend_string_release_ex(arch, false);
 		RETURN_THROWS();
 	}

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

 	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 4b49703b4ab..455f403b597 100644
--- a/ext/phar/stream.c
+++ b/ext/phar/stream.c
@@ -58,8 +58,7 @@ 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, *error;
-	size_t arch_len;
+	char *error;

 	if (strncasecmp(filename, "phar://", 7)) {
 		return NULL;
@@ -70,12 +69,14 @@ php_url* phar_parse_url(php_stream_wrapper *wrapper, const char *filename, const
 		}
 		return NULL;
 	}
+
 	zend_string *entry = NULL;
-	if (phar_split_fname(filename, strlen(filename), &arch, &arch_len, &entry, 2, (mode[0] == 'w' ? 2 : 0)) == FAILURE) {
+	const char *arch_error = NULL;
+	zend_string *arch = phar_split_fname_ex(filename, strlen(filename), &entry, 2, (mode[0] == 'w' ? 2 : 0), &arch_error);
+	if (!arch) {
 		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);
-				arch = NULL;
+			if (arch_error && !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_error);
 			} else {
 				php_stream_wrapper_log_error(wrapper, options, "phar error: invalid url or non-existent phar \"%s\"", filename);
 			}
@@ -84,8 +85,7 @@ php_url* phar_parse_url(php_stream_wrapper *wrapper, const char *filename, const
 	}
 	resource = ecalloc(1, sizeof(php_url));
 	resource->scheme = ZSTR_INIT_LITERAL("phar", 0);
-	resource->host = zend_string_init(arch, arch_len, 0);
-	efree(arch);
+	resource->host = arch;
 	resource->path = entry;

 #ifdef MBO_0
diff --git a/ext/phar/util.c b/ext/phar/util.c
index 8af0c488f5c..ef07460559c 100644
--- a/ext/phar/util.c
+++ b/ext/phar/util.c
@@ -266,8 +266,8 @@ zend_result phar_mount_entry(phar_archive_data *phar, const char *filename, size
 zend_string *phar_find_in_include_path(zend_string *filename, phar_archive_data **pphar) /* {{{ */
 {
 	zend_string *ret;
-	char *path, *arch;
-	size_t arch_len;
+	char *path;
+	zend_string *arch;
 	phar_archive_data *phar;

 	if (pphar) {
@@ -294,19 +294,22 @@ zend_string *phar_find_in_include_path(zend_string *filename, phar_archive_data
 		&& ZSTR_LEN(fname) - length_phar_protocol >= PHAR_G(last_phar_name_len)
 		&& !memcmp(ZSTR_VAL(fname) + length_phar_protocol, PHAR_G(last_phar_name), PHAR_G(last_phar_name_len))
 	) {
-		arch = estrndup(PHAR_G(last_phar_name), PHAR_G(last_phar_name_len));
-		arch_len = PHAR_G(last_phar_name_len);
+		arch = zend_string_init(PHAR_G(last_phar_name), PHAR_G(last_phar_name_len), false);
 		phar = PHAR_G(last_phar);
 		goto splitted;
 	}

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

 	if (*ZSTR_VAL(filename) == '.') {
-		if (FAILURE == phar_get_archive(&phar, arch, arch_len, NULL, 0, NULL)) {
-			efree(arch);
+		if (FAILURE == phar_get_archive(&phar, ZSTR_VAL(arch), ZSTR_LEN(arch), NULL, 0, NULL)) {
+			zend_string_release_ex(arch, false);
 			return NULL;
 		}
 splitted:
@@ -319,42 +322,43 @@ zend_string *phar_find_in_include_path(zend_string *filename, phar_archive_data
 			if (zend_hash_str_exists(&(phar->manifest), ZSTR_VAL(test) + 1, ZSTR_LEN(test) - 1)) {
 				ret = zend_string_concat3(
 					"phar://", strlen("phar://"),
-					arch, arch_len,
+					ZSTR_VAL(arch), ZSTR_LEN(arch),
 					ZSTR_VAL(test), ZSTR_LEN(test)
 				);
 				zend_string_release_ex(test, false);
-				efree(arch);
+				zend_string_release_ex(arch, false);
 				return ret;
 			}
 		} else {
 			if (zend_hash_exists(&(phar->manifest), test)) {
-				ret = strpprintf(0, "phar://%s/%s", arch, ZSTR_VAL(test));
+				ret = strpprintf(0, "phar://%s/%s", ZSTR_VAL(arch), ZSTR_VAL(test));
 				zend_string_release_ex(test, false);
-				efree(arch);
+				zend_string_release_ex(arch, false);
 				return ret;
 			}
 		}
 		zend_string_release_ex(test, false);
 	}

-	spprintf(&path, MAXPATHLEN + 1 + strlen(PG(include_path)), "phar://%s/%s%c%s", arch, PHAR_G(cwd), DEFAULT_DIR_SEPARATOR, PG(include_path));
-	efree(arch);
+	spprintf(&path, MAXPATHLEN + 1 + strlen(PG(include_path)), "phar://%s/%s%c%s", ZSTR_VAL(arch), PHAR_G(cwd), DEFAULT_DIR_SEPARATOR, PG(include_path));
+	zend_string_release_ex(arch, false);
 	ret = php_resolve_path(ZSTR_VAL(filename), ZSTR_LEN(filename), path);
 	efree(path);

 	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, 1, 0)) {
+		arch = phar_split_fname(ZSTR_VAL(fname), ZSTR_LEN(fname), NULL, 1, 0);
+		if (!arch) {
 			return ret;
 		}

-		*pphar = zend_hash_str_find_ptr(&(PHAR_G(phar_fname_map)), arch, arch_len);
+		*pphar = zend_hash_find_ptr(&(PHAR_G(phar_fname_map)), arch);

 		if (!*pphar && PHAR_G(manifest_cached)) {
-			*pphar = zend_hash_str_find_ptr(&cached_phars, arch, arch_len);
+			*pphar = zend_hash_find_ptr(&cached_phars, arch);
 		}

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

 	return ret;