Commit 1b204626c5e for php.net

commit 1b204626c5eb9dc6b15cb4d156cc14a2b633c7f9
Author: Gina Peter Banyard <girgias@php.net>
Date:   Sun Jul 19 18:48:03 2026 +0100

    main: convert doc_root global to zend_string*

    This remove a strlen() computation.

    While at it clarify path concatenation code by using the zend_string_concat{2|3} APIs rather than a memcpy and strncpy calls

diff --git a/main/fopen_wrappers.c b/main/fopen_wrappers.c
index c0f88ad3b5b..562cf1c63b9 100644
--- a/main/fopen_wrappers.c
+++ b/main/fopen_wrappers.c
@@ -359,7 +359,6 @@ PHPAPI int php_fopen_primary_script(zend_file_handle *file_handle)
 	char *path_info;
 	zend_string *filename = NULL;
 	zend_string *resolved_path = NULL;
-	size_t length;
 	bool orig_display_errors;

 	memset(file_handle, 0, sizeof(zend_file_handle));
@@ -372,7 +371,7 @@ PHPAPI int php_fopen_primary_script(zend_file_handle *file_handle)
 		if (s) {			/* if there is no path name after the file, do not bother */
 			char user[32];			/* to try open the directory */

-			length = s - (path_info + 2);
+			size_t length = s - (path_info + 2);
 			if (length > sizeof(user) - 1) {
 				length = sizeof(user) - 1;
 			}
@@ -421,19 +420,37 @@ PHPAPI int php_fopen_primary_script(zend_file_handle *file_handle)
 		}
 	} else
 #endif
-	if (PG(doc_root) && path_info && (length = strlen(PG(doc_root))) &&
-		IS_ABSOLUTE_PATH(PG(doc_root), length)) {
-		size_t path_len = strlen(path_info);
-		filename = zend_string_alloc(length + path_len + 2, 0);
-		memcpy(ZSTR_VAL(filename), PG(doc_root), length);
-		if (!IS_SLASH(ZSTR_VAL(filename)[length - 1])) {	/* length is never 0 */
-			ZSTR_VAL(filename)[length++] = PHP_DIR_SEPARATOR;
-		}
-		if (IS_SLASH(path_info[0])) {
-			length--;
-		}
-		strncpy(ZSTR_VAL(filename) + length, path_info, path_len + 1);
-		ZSTR_LEN(filename) = length + path_len;
+	if (PG(doc_root) && path_info && IS_ABSOLUTE_PATH(ZSTR_VAL(PG(doc_root)), ZSTR_LEN(PG(doc_root)))) {
+		const size_t path_len = strlen(path_info);
+
+		/* We need to concatenate two paths together, there are 3 situations:
+		 * - No trailing slash AND no leading slash
+		 * - Trailing slash AND leading slash
+		 * - Either a trailing slash OR a leading slash
+		 * In the first case we need to add a slash, in the second one we need to skip the leading slash,
+		 * and in the third we can just concatenate them together */
+		const unsigned int nb_slashes = IS_SLASH(ZSTR_VAL(PG(doc_root))[ZSTR_LEN(PG(doc_root)) - 1]) + IS_SLASH(path_info[0]);
+		switch (nb_slashes) {
+			case 0:
+				filename = zend_string_concat3(
+					ZSTR_VAL(PG(doc_root)), ZSTR_LEN(PG(doc_root)),
+					ZEND_STRL("/"),
+					path_info, path_len
+				);
+				break;
+			case 1:
+				filename = zend_string_concat2(
+					ZSTR_VAL(PG(doc_root)), ZSTR_LEN(PG(doc_root)),
+					path_info, path_len
+				);
+				break;
+			case 2:
+				filename = zend_string_concat2(
+					ZSTR_VAL(PG(doc_root)), ZSTR_LEN(PG(doc_root)),
+					path_info + 1, path_len -1
+				);
+				break;
+		}
 	} else if (SG(request_info).path_translated) {
 		filename = zend_string_init(SG(request_info).path_translated,
 			strlen(SG(request_info).path_translated), 0);
diff --git a/main/main.c b/main/main.c
index 3c079cb7fd7..5d26de4edc5 100644
--- a/main/main.c
+++ b/main/main.c
@@ -830,7 +830,7 @@ PHP_INI_BEGIN()

 	STD_PHP_INI_ENTRY("auto_append_file",		NULL,		PHP_INI_SYSTEM|PHP_INI_PERDIR,		OnUpdateString,			auto_append_file,		php_core_globals,	core_globals)
 	STD_PHP_INI_ENTRY("auto_prepend_file",		NULL,		PHP_INI_SYSTEM|PHP_INI_PERDIR,		OnUpdateString,			auto_prepend_file,		php_core_globals,	core_globals)
-	STD_PHP_INI_ENTRY("doc_root",				NULL,		PHP_INI_SYSTEM,		OnUpdateStringUnempty,	doc_root,				php_core_globals,	core_globals)
+	STD_PHP_INI_ENTRY("doc_root",				NULL,		PHP_INI_SYSTEM,		OnUpdateStrNotEmpty,	doc_root,				php_core_globals,	core_globals)
 	STD_PHP_INI_ENTRY("default_charset",		PHP_DEFAULT_CHARSET,	PHP_INI_ALL,	OnUpdateDefaultCharset,			default_charset,		sapi_globals_struct, sapi_globals)
 	STD_PHP_INI_ENTRY("default_mimetype",		SAPI_DEFAULT_MIMETYPE,	PHP_INI_ALL,	OnUpdateDefaultMimeTye,			default_mimetype,		sapi_globals_struct, sapi_globals)
 	STD_PHP_INI_ENTRY("internal_encoding",		NULL,			PHP_INI_ALL,	OnUpdateInternalEncoding,	internal_encoding,	php_core_globals, core_globals)
diff --git a/main/php_globals.h b/main/php_globals.h
index 333daab69ff..74639ffe0d4 100644
--- a/main/php_globals.h
+++ b/main/php_globals.h
@@ -76,7 +76,7 @@ struct _php_core_globals {

 	char *error_log;

-	char *doc_root;
+	zend_string *doc_root;
 	char *user_dir;
 	char *include_path;
 	char *open_basedir;
diff --git a/sapi/cgi/cgi_main.c b/sapi/cgi/cgi_main.c
index 841f45b207a..a79eada2728 100644
--- a/sapi/cgi/cgi_main.c
+++ b/sapi/cgi/cgi_main.c
@@ -1221,7 +1221,7 @@ static void init_request_info(fcgi_request *request)
 			size_t script_path_translated_len;

 			if (!env_document_root && PG(doc_root)) {
-				env_document_root = CGI_PUTENV("DOCUMENT_ROOT", PG(doc_root));
+				env_document_root = CGI_PUTENV("DOCUMENT_ROOT", ZSTR_VAL(PG(doc_root)));
 				/* fix docroot */
 				TRANSLATE_SLASHES(env_document_root);
 			}
diff --git a/sapi/fpm/fpm/fpm_main.c b/sapi/fpm/fpm/fpm_main.c
index 9b7cbcf16f0..655d66ce5f0 100644
--- a/sapi/fpm/fpm/fpm_main.c
+++ b/sapi/fpm/fpm/fpm_main.c
@@ -1078,7 +1078,7 @@ static void init_request_info(void)
 			int script_path_translated_len;

 			if (!env_document_root && PG(doc_root)) {
-				env_document_root = FCGI_PUTENV(request, "DOCUMENT_ROOT", PG(doc_root));
+				env_document_root = FCGI_PUTENV(request, "DOCUMENT_ROOT", ZSTR_VAL(PG(doc_root)));
 			}

 			if (!apache_was_here && env_path_translated != NULL && env_redirect_url != NULL &&