From 44109e9d6678f24efac627947560c47809de2645 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Wed, 2 Sep 2026 11:14:44 +0200 Subject: [PATCH 1/2] PHP 8.6 | Make "characters to be trimmed" explicit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit By default, the PHP native `[lr]trim()` functions, trim ASCII whitespace and the NUL byte character. PHP 8.6 changes the default value of the `$characters` parameter to also include the form feed - `"\f"` - character, which was previously not trimmed. ⚠️ _Keep in mind that `[lr]trim()` only operates on the leading and/or trailing characters for a text string. It does not affect the characters in the "middle" !_ Requests uses `[lr]trim()` in various places throughout the codebase. To make this code PHP cross-version compatible, this commit makes the following changes: * Introduces a new `final` `Trim` class containing two class constants to represent the different PHP native default values for the `$characters` parameter. This class has been explicitly marked as not part of the public API to allow for changing that class to an `enum` once support for PHP < 8.1 has been dropped. * Makes the `$characters` being trimmed explicit in each of the `[lr]trim()` function calls (if it wasn't already). To determine which characters should be trimmed, the following rule of thumb has been used: _"Use the PHP 8.6 default (`Trim::WHITESPACE_CHARS`), except when the trimming may be subject to an RFC or other documented rules, in which case use the PHP < 8.6 default (`Trim::WHITESPACE_CHARS_NO_FF`)"_ That way, we preserve existing behaviour in "important" places, while benefitting from the new default value everywhere else. _Note: this PR does not update the tests. Tests should be updated to safeguard _changed_ behaviour, but the changes in this PR do not constitute significantly changed behaviour for those places where the behaviour was "changed" (where `Trim::WHITESPACE_CHARS` was used). And where the change _could_ be significant, the behaviour was not changed (`Trim::WHITESPACE_CHARS_NO_FF`)._ As a follow-up to this PR, an issue should be opened to review the trimming for those function calls where the behaviour was **_not_** changed in this PR (`Trim::WHITESPACE_CHARS_NO_FF`), against the applicable RFCs or other documentation. Refs: * https://wiki.php.net/rfc/trim_form_feed * https://www.php.net/manual/en/function.trim.php --- src/Cookie.php | 11 ++++---- src/Requests.php | 13 +++++----- src/Ssl.php | 5 ++-- src/Transport/Curl.php | 3 ++- src/Transport/Fsockopen.php | 5 ++-- src/Utility/Trim.php | 51 +++++++++++++++++++++++++++++++++++++ 6 files changed, 72 insertions(+), 16 deletions(-) create mode 100644 src/Utility/Trim.php diff --git a/src/Cookie.php b/src/Cookie.php index 3cc0efcd4..9075c25e7 100644 --- a/src/Cookie.php +++ b/src/Cookie.php @@ -14,6 +14,7 @@ use WpOrg\Requests\Response\Headers; use WpOrg\Requests\Utility\CaseInsensitiveDictionary; use WpOrg\Requests\Utility\InputValidator; +use WpOrg\Requests\Utility\Trim; /** * Cookie storage object @@ -440,7 +441,7 @@ public static function parse($cookie_header, $name = '', $reference_time = null) } if (is_string($name)) { - $name = trim($name); + $name = trim($name, Trim::WHITESPACE_CHARS_NO_FF); } if ($name !== '' && InputValidator::is_valid_rfc2616_token($name) === false) { @@ -464,8 +465,8 @@ public static function parse($cookie_header, $name = '', $reference_time = null) list($name, $value) = explode('=', $kvparts, 2); } - $name = trim($name); - $value = trim($value); + $name = trim($name, Trim::WHITESPACE_CHARS_NO_FF); + $value = trim($value, Trim::WHITESPACE_CHARS_NO_FF); if ($name !== '' && InputValidator::is_valid_rfc2616_token($name) === false) { throw InvalidArgument::create(2, '$name', 'integer|string and conform to RFC 2616', gettype($name)); @@ -481,10 +482,10 @@ public static function parse($cookie_header, $name = '', $reference_time = null) $part_value = true; } else { list($part_key, $part_value) = explode('=', $part, 2); - $part_value = trim($part_value); + $part_value = trim($part_value, Trim::WHITESPACE_CHARS_NO_FF); } - $part_key = trim($part_key); + $part_key = trim($part_key, Trim::WHITESPACE_CHARS_NO_FF); $attributes[$part_key] = $part_value; } } diff --git a/src/Requests.php b/src/Requests.php index 13e3fe13a..11fe2f6f6 100644 --- a/src/Requests.php +++ b/src/Requests.php @@ -22,6 +22,7 @@ use WpOrg\Requests\Transport\Curl; use WpOrg\Requests\Transport\Fsockopen; use WpOrg\Requests\Utility\InputValidator; +use WpOrg\Requests\Utility\Trim; /** * Requests for PHP @@ -762,7 +763,7 @@ protected static function parse_response($headers, $url, $req_headers, $req_data foreach ($headers as $header) { list($key, $value) = explode(':', $header, 2); - $value = trim($value); + $value = trim($value, Trim::WHITESPACE_CHARS_NO_FF); preg_replace('#(\s+)#i', ' ', $value); $return->headers[$key] = $value; } @@ -851,7 +852,7 @@ public static function parse_multiple(&$response, $request) { * @return string Decoded body */ protected static function decode_chunked($data) { - if (!preg_match('/^([0-9a-f]+)(?:;(?:[\w-]*)(?:=(?:(?:[\w-]*)*|"(?:[^\r\n])*"))?)*\r\n/i', trim($data))) { + if (!preg_match('/^([0-9a-f]+)(?:;(?:[\w-]*)(?:=(?:(?:[\w-]*)*|"(?:[^\r\n])*"))?)*\r\n/i', trim($data, Trim::WHITESPACE_CHARS_NO_FF))) { return $data; } @@ -865,7 +866,7 @@ protected static function decode_chunked($data) { return $data; } - $length = hexdec(trim($matches[1])); + $length = hexdec(trim($matches[1], Trim::WHITESPACE_CHARS_NO_FF)); if ($length === 0) { // Ignore trailer headers return $decoded; @@ -875,7 +876,7 @@ protected static function decode_chunked($data) { $decoded .= substr($encoded, $chunk_length, $length); $encoded = substr($encoded, $chunk_length + $length + 2); - if (trim($encoded) === '0' || empty($encoded)) { + if (trim($encoded, Trim::WHITESPACE_CHARS_NO_FF) === '0' || empty($encoded)) { return $decoded; } } @@ -922,7 +923,7 @@ public static function decompress($data) { throw InvalidArgument::create(1, '$data', 'string', gettype($data)); } - if (trim($data) === '') { + if (trim($data, Trim::WHITESPACE_CHARS_NO_FF) === '') { // Empty body does not need further processing. return $data; } @@ -989,7 +990,7 @@ public static function compatible_gzinflate($gz_data) { throw InvalidArgument::create(1, '$gz_data', 'string', gettype($gz_data)); } - if (trim($gz_data) === '') { + if (trim($gz_data, Trim::WHITESPACE_CHARS_NO_FF) === '') { return false; } diff --git a/src/Ssl.php b/src/Ssl.php index debbea64a..93a64faf3 100644 --- a/src/Ssl.php +++ b/src/Ssl.php @@ -11,6 +11,7 @@ use WpOrg\Requests\Exception\InvalidArgument; use WpOrg\Requests\Utility\InputValidator; +use WpOrg\Requests\Utility\Trim; /** * SSL utilities for Requests @@ -49,7 +50,7 @@ public static function verify_certificate($host, $cert) { if (!empty($cert['extensions']['subjectAltName'])) { $altnames = explode(',', $cert['extensions']['subjectAltName']); foreach ($altnames as $altname) { - $altname = trim($altname); + $altname = trim($altname, Trim::WHITESPACE_CHARS_NO_FF); if (strpos($altname, 'DNS:') !== 0) { continue; } @@ -57,7 +58,7 @@ public static function verify_certificate($host, $cert) { $has_dns_alt = true; // Strip the 'DNS:' prefix and trim whitespace - $altname = trim(substr($altname, 4)); + $altname = trim(substr($altname, 4), Trim::WHITESPACE_CHARS_NO_FF); // Check for a match if (self::match_domain($host, $altname) === true) { diff --git a/src/Transport/Curl.php b/src/Transport/Curl.php index 18af2331e..215b77e97 100644 --- a/src/Transport/Curl.php +++ b/src/Transport/Curl.php @@ -18,6 +18,7 @@ use WpOrg\Requests\Requests; use WpOrg\Requests\Transport; use WpOrg\Requests\Utility\InputValidator; +use WpOrg\Requests\Utility\Trim; /** * HTTP transport using libcurl. @@ -500,7 +501,7 @@ public function process_response($response, $options) { if ($options['filename'] !== false && $this->stream_handle) { fclose($this->stream_handle); - $this->headers = trim($this->headers); + $this->headers = trim($this->headers, Trim::WHITESPACE_CHARS_NO_FF); } else { $this->headers .= $response; } diff --git a/src/Transport/Fsockopen.php b/src/Transport/Fsockopen.php index f6ef18f8f..d712b7330 100644 --- a/src/Transport/Fsockopen.php +++ b/src/Transport/Fsockopen.php @@ -18,6 +18,7 @@ use WpOrg\Requests\Transport; use WpOrg\Requests\Utility\CaseInsensitiveDictionary; use WpOrg\Requests\Utility\InputValidator; +use WpOrg\Requests\Utility\Trim; /** * fsockopen HTTP transport @@ -182,7 +183,7 @@ public function request($url, $headers = [], $data = [], $options = []) { if (!$socket) { if ($errno === 0) { // Connection issue - throw new Exception(rtrim($this->connect_error), 'fsockopen.connect_error'); + throw new Exception(rtrim($this->connect_error, Trim::WHITESPACE_CHARS), 'fsockopen.connect_error'); } throw new Exception($errstr, 'fsockopenerror', null, $errno); @@ -492,7 +493,7 @@ public function verify_certificate_from_context($host, $context) { // If we don't have SSL options, then we couldn't make the connection at // all if (empty($meta) || empty($meta['ssl']) || empty($meta['ssl']['peer_certificate'])) { - throw new Exception(rtrim($this->connect_error), 'ssl.connect_error'); + throw new Exception(rtrim($this->connect_error, Trim::WHITESPACE_CHARS), 'ssl.connect_error'); } $cert = openssl_x509_parse($meta['ssl']['peer_certificate']); diff --git a/src/Utility/Trim.php b/src/Utility/Trim.php new file mode 100644 index 000000000..862cb1ac9 --- /dev/null +++ b/src/Utility/Trim.php @@ -0,0 +1,51 @@ += 8.6. + * + * @var string + */ + const WHITESPACE_CHARS = " \f\n\r\t\v\x00"; +} From 6d9e4192f6714c652ae80cb71f590d6073234b9d Mon Sep 17 00:00:00 2001 From: jrfnl Date: Wed, 2 Sep 2026 11:18:22 +0200 Subject: [PATCH 2/2] PHP 8.6 | GH Pages: make "characters to be trimmed" explicit The script to convert certain markdown documents to documents suitable for use in the GH Pages website, makes some calls to `trim()` functions. In all cases, form feed characters should be stripped (like PHP 8.6 will do by default). This commit standardized on the PHP 8.6 behaviour. Note: this commit does not use the new `Trim` class as Requests isn't loaded when running this script (stand-alone). Refs: * https://wiki.php.net/rfc/trim_form_feed * https://www.php.net/manual/en/function.trim.php --- build/ghpages/UpdateMarkdown.php | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/build/ghpages/UpdateMarkdown.php b/build/ghpages/UpdateMarkdown.php index 080851af5..86119c267 100644 --- a/build/ghpages/UpdateMarkdown.php +++ b/build/ghpages/UpdateMarkdown.php @@ -20,6 +20,7 @@ * * @package Requests\GHPages * + * @phpcs:disable PHPCompatibility.Classes.NewConstVisibility.Found * @phpcs:disable PHPCompatibility.FunctionDeclarations.NewParamTypeDeclarations.stringFound * @phpcs:disable PHPCompatibility.FunctionDeclarations.NewReturnTypeDeclarations.intFound * @phpcs:disable PHPCompatibility.FunctionDeclarations.NewReturnTypeDeclarations.stringFound @@ -28,6 +29,13 @@ */ class UpdateMarkdown { + /** + * The ASCII whitespace characters and the null byte. + * + * @var string + */ + private const WHITESPACE_CHARS = " \f\n\r\t\v\x00"; + /** * Target directory for the updated/transformed files. * @@ -282,7 +290,7 @@ private function update_docs_navigation(string $source): void { /* * Create the docs index file. */ - $docs_index = trim($parts[0]); + $docs_index = trim($parts[0], self::WHITESPACE_CHARS); // Grab the title. $title = $this->get_title_from_contents($contents); @@ -300,7 +308,7 @@ private function update_docs_navigation(string $source): void { /* * Create the docs navigation file. */ - $navigation = trim($parts[1]); + $navigation = trim($parts[1], self::WHITESPACE_CHARS); // Write the file. $target = $this->target . '/_includes/navigation.md'; @@ -349,7 +357,7 @@ private function put_contents(string $target, string $contents, string $type = ' } // phpcs:enable WordPress // Make sure the file always ends on a new line. - $contents = rtrim($contents) . "\n"; + $contents = rtrim($contents, self::WHITESPACE_CHARS) . "\n"; if (file_put_contents($target, $contents) === false) { throw new RuntimeException(sprintf('Failed to write %s to target location: %s', $type, $target)); } @@ -363,7 +371,7 @@ private function put_contents(string $target, string $contents, string $type = ' * @return string */ private function get_title_from_contents(string $contents): string { - return trim(substr($contents, 0, (strpos($contents, '===') - 1))); + return trim(substr($contents, 0, (strpos($contents, '===') - 1)), self::WHITESPACE_CHARS); } /**