Commit b33fee51fb7 for php.net

commit b33fee51fb7ab5b919faf71507c45e86cf2bd978
Author: Gina Peter Banyard <girgias@php.net>
Date:   Sat Apr 18 16:27:22 2026 +0100

    ext/phar: refactor phar_entry_info.link field (#21790)

    - Rename field to symlink so that what this field represents is clear
    - Convert it from a char* to a zend_string*
    - Refactor the implementation of phar_get_link_location() to always return a "new" zend_string*
    - Refactor the implementation of phar_get_link_source() to make it more legible

diff --git a/ext/phar/func_interceptors.c b/ext/phar/func_interceptors.c
index 20ce0e8d725..ae52e7189bd 100644
--- a/ext/phar/func_interceptors.c
+++ b/ext/phar/func_interceptors.c
@@ -578,7 +578,7 @@ splitted:;
 			if (!data->is_dir) {
 				sb.st_size = data->uncompressed_filesize;
 				sb.st_mode = data->flags & PHAR_ENT_PERM_MASK;
-				if (data->link) {
+				if (data->symlink) {
 					sb.st_mode |= S_IFREG|S_IFLNK; /* regular file */
 				} else {
 					sb.st_mode |= S_IFREG; /* regular file */
@@ -591,7 +591,7 @@ splitted:;
 				sb.st_size = 0;
 				sb.st_mode = data->flags & PHAR_ENT_PERM_MASK;
 				sb.st_mode |= S_IFDIR; /* regular directory */
-				if (data->link) {
+				if (data->symlink) {
 					sb.st_mode |= S_IFLNK;
 				}
 				/* timestamp is just the timestamp when this was added to the phar */
@@ -803,7 +803,7 @@ PHP_FUNCTION(phar_is_link) /* {{{ */
 				}
 				zend_string_release_ex(entry, false);
 				if (etemp) {
-					RETURN_BOOL(etemp->link);
+					RETURN_BOOL(etemp->symlink);
 				}
 			}
 			RETURN_FALSE;
diff --git a/ext/phar/phar.c b/ext/phar/phar.c
index 3008316f627..5829d32e615 100644
--- a/ext/phar/phar.c
+++ b/ext/phar/phar.c
@@ -374,9 +374,9 @@ void destroy_phar_manifest_entry_int(phar_entry_info *entry) /* {{{ */

 	zend_string_release_ex(entry->filename, entry->is_persistent);

-	if (entry->link) {
-		pefree(entry->link, entry->is_persistent);
-		entry->link = 0;
+	if (entry->symlink) {
+		zend_string_release_ex(entry->symlink, entry->is_persistent);
+		entry->symlink = NULL;
 	}

 	if (entry->tmp) {
diff --git a/ext/phar/phar_internal.h b/ext/phar/phar_internal.h
index 34f221f43e0..db2856309fc 100644
--- a/ext/phar/phar_internal.h
+++ b/ext/phar/phar_internal.h
@@ -217,7 +217,7 @@ typedef struct _phar_entry_info {
 	unsigned int             fileinfo_lock_count;
 	char                     *tmp;
 	phar_archive_data        *phar;
-	char                     *link; /* symbolic link to another file */
+	zend_string              *symlink;
 	char                     tar_type;
 	/* position in the manifest */
 	uint32_t                     manifest_pos;
diff --git a/ext/phar/phar_object.c b/ext/phar/phar_object.c
index 07058eefc46..0af02748407 100644
--- a/ext/phar/phar_object.c
+++ b/ext/phar/phar_object.c
@@ -1902,7 +1902,7 @@ static zend_result phar_copy_file_contents(phar_entry_info *entry, php_stream *f
 	char *error;
 	zend_off_t offset;

-	ZEND_ASSERT(!entry->link);
+	ZEND_ASSERT(!entry->symlink);

 	if (FAILURE == phar_open_entry_fp(entry, &error, true)) {
 		if (error) {
@@ -2243,8 +2243,8 @@ static zend_object *phar_convert_to_other(phar_archive_data *source, int convert

 		newentry = *entry;

-		if (newentry.link) {
-			newentry.link = estrdup(newentry.link);
+		if (newentry.symlink) {
+			zend_string_addref(newentry.symlink);
 			goto no_copy;
 		}

diff --git a/ext/phar/stream.c b/ext/phar/stream.c
index 455f403b597..f929aef4fb9 100644
--- a/ext/phar/stream.c
+++ b/ext/phar/stream.c
@@ -368,7 +368,7 @@ static ssize_t phar_stream_read(php_stream *stream, char *buf, size_t count) /*
 	ssize_t got;
 	phar_entry_info *entry;

-	if (data->internal_file->link) {
+	if (data->internal_file->symlink) {
 		entry = phar_get_link_source(data->internal_file);
 	} else {
 		entry = data->internal_file;
@@ -400,7 +400,7 @@ static int phar_stream_seek(php_stream *stream, zend_off_t offset, int whence, z
 	int res;
 	zend_ulong temp;

-	if (data->internal_file->link) {
+	if (data->internal_file->symlink) {
 		entry = phar_get_link_source(data->internal_file);
 	} else {
 		entry = data->internal_file;
@@ -838,7 +838,8 @@ static int phar_wrapper_rename(php_stream_wrapper *wrapper, const char *url_from
 		entry->is_deleted = 1;
 		entry->fp = NULL;
 		ZVAL_UNDEF(&entry->metadata_tracker.val);
-		entry->link = entry->tmp = NULL;
+		entry->symlink = NULL;
+		entry->tmp = NULL;
 		source = entry;

 		/* add to the manifest, and then store the pointer to the new guy in entry
diff --git a/ext/phar/tar.c b/ext/phar/tar.c
index ef356bfc7a2..844f613983f 100644
--- a/ext/phar/tar.c
+++ b/ext/phar/tar.c
@@ -492,8 +492,8 @@ zend_result phar_parse_tarfile(php_stream* fp, const char *fname, size_t fname_l
 			entry.is_dir = 0;
 		}

-		entry.link = NULL;
-		/* link field is null-terminated unless it has 100 non-null chars.
+		entry.symlink = NULL;
+		/* linkname field is null-terminated unless it has 100 non-null chars.
 		 * Thus we cannot use strlen. */
 		linkname_len = zend_strnlen(hdr->linkname, 100);
 		if (entry.tar_type == TAR_LINK) {
@@ -506,9 +506,9 @@ zend_result phar_parse_tarfile(php_stream* fp, const char *fname, size_t fname_l
 				phar_destroy_phar_data(myphar);
 				return FAILURE;
 			}
-			entry.link = estrndup(hdr->linkname, linkname_len);
+			entry.symlink = zend_string_init(hdr->linkname, linkname_len, false);
 		} else if (entry.tar_type == TAR_SYMLINK) {
-			entry.link = estrndup(hdr->linkname, linkname_len);
+			entry.symlink = zend_string_init(hdr->linkname, linkname_len, false);
 		}
 		phar_set_inode(&entry);

@@ -779,10 +779,10 @@ static int phar_tar_writeheaders_int(phar_entry_info *entry, void *argument) /*
 	/* calc checksum */
 	header.typeflag = entry->tar_type;

-	if (entry->link) {
-		if (strlcpy(header.linkname, entry->link, sizeof(header.linkname)) >= sizeof(header.linkname)) {
+	if (entry->symlink) {
+		if (strlcpy(header.linkname, ZSTR_VAL(entry->symlink), sizeof(header.linkname)) >= sizeof(header.linkname)) {
 			if (fp->error) {
-				spprintf(fp->error, 4096, "tar-based phar \"%s\" cannot be created, link \"%s\" is too long for format", entry->phar->fname, entry->link);
+				spprintf(fp->error, 4096, "tar-based phar \"%s\" cannot be created, link \"%s\" is too long for format", entry->phar->fname, ZSTR_VAL(entry->symlink));
 			}
 			return ZEND_HASH_APPLY_STOP;
 		}
diff --git a/ext/phar/util.c b/ext/phar/util.c
index 43ed4dd2482..bbfcde8d868 100644
--- a/ext/phar/util.c
+++ b/ext/phar/util.c
@@ -38,50 +38,50 @@ static zend_result phar_call_openssl_signverify(bool is_sign, php_stream *fp, ze
 #endif

 /* for links to relative location, prepend cwd of the entry */
-static char *phar_get_link_location(phar_entry_info *entry) /* {{{ */
+static zend_string *phar_get_link_location(phar_entry_info *entry) /* {{{ */
 {
-	char *p, *ret = NULL;
+	ZEND_ASSERT(entry->symlink);

-	ZEND_ASSERT(entry->link);
-
-	if (entry->link[0] == '/') {
-		return estrdup(entry->link + 1);
+	if (ZSTR_VAL(entry->symlink)[0] == '/') {
+		return zend_string_init(ZSTR_VAL(entry->symlink) + 1, ZSTR_LEN(entry->symlink) - 1, false);
 	}
-	p = strrchr(ZSTR_VAL(entry->filename), '/');
+	const char *p = strrchr(ZSTR_VAL(entry->filename), '/');
 	if (p) {
 		/* Important: don't modify the original `p` data because it is a shared string. */
 		zend_string *new_name = zend_string_init(ZSTR_VAL(entry->filename), p - ZSTR_VAL(entry->filename), false);
-		spprintf(&ret, 0, "%s/%s", ZSTR_VAL(new_name), entry->link);
+		zend_string *location = zend_string_concat3(
+			ZSTR_VAL(new_name), ZSTR_LEN(new_name),
+			ZEND_STRL("/"),
+			ZSTR_VAL(entry->symlink), ZSTR_LEN(entry->symlink)
+		);
 		zend_string_release(entry->filename);
 		entry->filename = new_name;
-		return ret;
+		return location;
 	}
-	return entry->link;
+	return zend_string_copy(entry->symlink);
 }
 /* }}} */

 phar_entry_info *phar_get_link_source(phar_entry_info *entry) /* {{{ */
 {
 	phar_entry_info *link_entry;
-	char *link;

-	if (!entry->link) {
+	if (!entry->symlink) {
 		return entry;
 	}

-	link = phar_get_link_location(entry);
-	if (NULL != (link_entry = zend_hash_str_find_ptr(&(entry->phar->manifest), entry->link, strlen(entry->link))) ||
-		NULL != (link_entry = zend_hash_str_find_ptr(&(entry->phar->manifest), link, strlen(link)))) {
-		if (link != entry->link) {
-			efree(link);
-		}
+	link_entry = zend_hash_find_ptr(&(entry->phar->manifest), entry->symlink);
+	if (link_entry) {
 		return phar_get_link_source(link_entry);
-	} else {
-		if (link != entry->link) {
-			efree(link);
-		}
-		return NULL;
 	}
+
+	zend_string *link = phar_get_link_location(entry);
+	link_entry = zend_hash_find_ptr(&(entry->phar->manifest), link);
+	zend_string_release(link);
+	if (link_entry) {
+		return phar_get_link_source(link_entry);
+	}
+	return NULL;
 }
 /* }}} */

@@ -96,7 +96,7 @@ static php_stream *phar_get_entrypufp(const phar_entry_info *entry)
 /* retrieve a phar_entry_info's current file pointer for reading contents */
 php_stream *phar_get_efp(phar_entry_info *entry, bool follow_links) /* {{{ */
 {
-	if (follow_links && entry->link) {
+	if (follow_links && entry->symlink) {
 		phar_entry_info *link_entry = phar_get_link_source(entry);

 		if (link_entry && link_entry != entry) {
@@ -387,9 +387,9 @@ static ZEND_ATTRIBUTE_NONNULL zend_result phar_create_writeable_entry(phar_archi
 	}

 	/* open a new temp file for writing */
-	if (entry->link) {
-		efree(entry->link);
-		entry->link = NULL;
+	if (entry->symlink) {
+		zend_string_release(entry->symlink);
+		entry->symlink = NULL;
 		entry->tar_type = (entry->is_tar ? TAR_FILE : '\0');
 	}

@@ -444,9 +444,9 @@ ZEND_ATTRIBUTE_NONNULL static zend_result phar_separate_entry_fp(phar_entry_info
 		return FAILURE;
 	}

-	if (entry->link) {
-		efree(entry->link);
-		entry->link = NULL;
+	if (entry->symlink) {
+		zend_string_release(entry->symlink);
+		entry->symlink = NULL;
 		entry->tar_type = (entry->is_tar ? TAR_FILE : '\0');
 	}

@@ -559,9 +559,9 @@ ZEND_ATTRIBUTE_NONNULL zend_result phar_get_entry_data(phar_entry_data **ret, co
 		}
 	} else {
 		if (for_write) {
-			if (entry->link) {
-				efree(entry->link);
-				entry->link = NULL;
+			if (entry->symlink) {
+				zend_string_release(entry->symlink);
+				entry->symlink = NULL;
 				entry->tar_type = (entry->is_tar ? TAR_FILE : '\0');
 			}

@@ -586,7 +586,7 @@ ZEND_ATTRIBUTE_NONNULL zend_result phar_get_entry_data(phar_entry_data **ret, co
 	(*ret)->phar = phar;
 	(*ret)->internal_file = entry;
 	(*ret)->fp = phar_get_efp(entry, true);
-	if (entry->link) {
+	if (entry->symlink) {
 		phar_entry_info *link = phar_get_link_source(entry);
 		if(!link) {
 			efree(*ret);
@@ -740,9 +740,9 @@ ZEND_ATTRIBUTE_NONNULL zend_result phar_copy_entry_fp(phar_entry_info *source, p
 		return FAILURE;
 	}

-	if (dest->link) {
-		efree(dest->link);
-		dest->link = NULL;
+	if (dest->symlink) {
+		zend_string_release(dest->symlink);
+		dest->symlink = NULL;
 		dest->tar_type = (dest->is_tar ? TAR_FILE : '\0');
 	}

@@ -807,7 +807,7 @@ ZEND_ATTRIBUTE_NONNULL zend_result phar_open_entry_fp(phar_entry_info *entry, ch
 	php_stream *ufp;
 	phar_entry_data dummy;

-	if (follow_links && entry->link) {
+	if (follow_links && entry->symlink) {
 		phar_entry_info *link_entry = phar_get_link_source(entry);
 		if (link_entry && link_entry != entry) {
 			return phar_open_entry_fp(link_entry, error, true);
@@ -1995,8 +1995,8 @@ static int phar_update_cached_entry(zval *data, void *argument) /* {{{ */

 	entry->phar = (phar_archive_data *)argument;

-	if (entry->link) {
-		entry->link = estrdup(entry->link);
+	if (entry->symlink) {
+		zend_string_addref(entry->symlink);
 	}

 	if (entry->tmp) {