Commit da1e89fd3db for php.net

commit da1e89fd3db0b9d2017976d774270ee7ce3b35a7
Author: Weilin Du <108666168+LamentXU123@users.noreply.github.com>
Date:   Tue Mar 10 05:28:50 2026 +0800

    RFC: Add Form Feed in Trim Functions (#20788)

    RFC: https://wiki.php.net/rfc/trim_form_feed

    Resolves GH-20783.

diff --git a/NEWS b/NEWS
index 482dfcb0913..ae0f6f3e8ff 100644
--- a/NEWS
+++ b/NEWS
@@ -122,6 +122,8 @@ PHP                                                                        NEWS
 - Standard:
   . Fixed bug GH-19926 (reset internal pointer earlier while splicing array
     while COW violation flag is still set). (alexandre-daubois)
+  . Added form feed (\f) in the default trimmed characters of trim(), rtrim()
+    and ltrim(). (Weilin Du)
   . Invalid mode values now throw in array_filter() instead of being silently
     defaulted to 0. (Jorg Sowa)
   . Fixed bug GH-21058 (error_log() crashes with message_type 3 and
diff --git a/UPGRADING b/UPGRADING
index 732d6f1d485..595a49e41e4 100644
--- a/UPGRADING
+++ b/UPGRADING
@@ -47,6 +47,8 @@ PHP 8.6 UPGRADE NOTES
 - Standard:
   . Invalid mode values now throw in array_filter() instead of being silently
     defaulted to 0.
+  . Form feed (\f) is now added in the default trimmed characters of trim(),
+    rtrim() and ltrim(). RFC: https://wiki.php.net/rfc/trim_form_feed

 ========================================
 2. New Features
diff --git a/ext/standard/basic_functions.stub.php b/ext/standard/basic_functions.stub.php
index 6fa0d47c7bd..6d0c565fc2d 100644
--- a/ext/standard/basic_functions.stub.php
+++ b/ext/standard/basic_functions.stub.php
@@ -2318,16 +2318,16 @@ function strcoll(string $string1, string $string2): int {}
  * @frameless-function {"arity": 1}
  * @frameless-function {"arity": 2}
  */
-function trim(string $string, string $characters = " \n\r\t\v\0"): string {}
+function trim(string $string, string $characters = " \f\n\r\t\v\0"): string {}

 /** @compile-time-eval */
-function rtrim(string $string, string $characters = " \n\r\t\v\0"): string {}
+function rtrim(string $string, string $characters = " \f\n\r\t\v\0"): string {}

 /** @alias rtrim */
-function chop(string $string, string $characters = " \n\r\t\v\0"): string {}
+function chop(string $string, string $characters = " \f\n\r\t\v\0"): string {}

 /** @compile-time-eval */
-function ltrim(string $string, string $characters = " \n\r\t\v\0"): string {}
+function ltrim(string $string, string $characters = " \f\n\r\t\v\0"): string {}

 /**
  * @compile-time-eval
diff --git a/ext/standard/basic_functions_arginfo.h b/ext/standard/basic_functions_arginfo.h
index e467710f72f..d0109fa27c9 100644
Binary files a/ext/standard/basic_functions_arginfo.h and b/ext/standard/basic_functions_arginfo.h differ
diff --git a/ext/standard/basic_functions_decl.h b/ext/standard/basic_functions_decl.h
index 9e6fb0def44..139b47f2444 100644
Binary files a/ext/standard/basic_functions_decl.h and b/ext/standard/basic_functions_decl.h differ
diff --git a/ext/standard/string.c b/ext/standard/string.c
index d146b4534e2..7d609a032dd 100644
--- a/ext/standard/string.c
+++ b/ext/standard/string.c
@@ -515,11 +515,16 @@ static inline zend_result php_charmask(const unsigned char *input, size_t len, c
 }
 /* }}} */

+static zend_always_inline bool php_is_whitespace(unsigned char c)
+{
+	return c <= ' ' && (c == ' ' || c == '\f' || c == '\n' || c == '\r' || c == '\t' || c == '\v' || c == '\0');
+}
+
 /* {{{ php_trim_int()
  * mode 1 : trim left
  * mode 2 : trim right
  * mode 3 : trim left and right
- * what indicates which chars are to be trimmed. NULL->default (' \t\n\r\v\0')
+ * what indicates which chars are to be trimmed. NULL->default (' \f\t\n\r\v\0')
  */
 static zend_always_inline zend_string *php_trim_int(zend_string *str, const char *what, size_t what_len, int mode)
 {
@@ -573,10 +578,7 @@ static zend_always_inline zend_string *php_trim_int(zend_string *str, const char
 	} else {
 		if (mode & 1) {
 			while (start != end) {
-				unsigned char c = (unsigned char)*start;
-
-				if (c <= ' ' &&
-				    (c == ' ' || c == '\n' || c == '\r' || c == '\t' || c == '\v' || c == '\0')) {
+				if (php_is_whitespace((unsigned char)*start)) {
 					start++;
 				} else {
 					break;
@@ -585,10 +587,7 @@ static zend_always_inline zend_string *php_trim_int(zend_string *str, const char
 		}
 		if (mode & 2) {
 			while (start != end) {
-				unsigned char c = (unsigned char)*(end-1);
-
-				if (c <= ' ' &&
-				    (c == ' ' || c == '\n' || c == '\r' || c == '\t' || c == '\v' || c == '\0')) {
+				if (php_is_whitespace((unsigned char)*(end-1))) {
 					end--;
 				} else {
 					break;
@@ -611,7 +610,7 @@ static zend_always_inline zend_string *php_trim_int(zend_string *str, const char
  * mode 1 : trim left
  * mode 2 : trim right
  * mode 3 : trim left and right
- * what indicates which chars are to be trimmed. NULL->default (' \t\n\r\v\0')
+ * what indicates which chars are to be trimmed. NULL->default (' \f\t\n\r\v\0')
  */
 PHPAPI zend_string *php_trim(zend_string *str, const char *what, size_t what_len, int mode)
 {
diff --git a/ext/standard/tests/strings/trim.phpt b/ext/standard/tests/strings/trim.phpt
index fd0c26d7794..9ce5e1dfff0 100644
--- a/ext/standard/tests/strings/trim.phpt
+++ b/ext/standard/tests/strings/trim.phpt
@@ -18,6 +18,9 @@
 var_dump("ABC\x50\xC1" === trim("ABC\x50\xC1\x60\x90","\x51..\xC0"));
 var_dump("ABC\x50" === trim("ABC\x50\xC1\x60\x90","\x51..\xC1"));
 var_dump("ABC" === trim("ABC\x50\xC1\x60\x90","\x50..\xC1"));
+var_dump("ABC" ===  trim("\fABC\f"));
+var_dump("ABC" === ltrim("\fABC"));
+var_dump("ABC" === rtrim("ABC\f"));

 ?>
 --EXPECT--
@@ -36,3 +39,6 @@
 bool(true)
 bool(true)
 bool(true)
+bool(true)
+bool(true)
+bool(true)