From cfd713aad908d8b32b09c027a151725761a9940d Mon Sep 17 00:00:00 2001 From: Khokan Sardar Date: Thu, 13 Aug 2026 19:02:17 +0530 Subject: [PATCH] General: Align wp_is_stream() with PHP's stream wrapper matching. PHP resolves a scheme by looking it up as given, then retrying once with the scheme lowercased. wp_is_stream() only performed the exact match, so paths such as `FILE:///path/to/file` were reported as non-streams even though PHP reads them. Match the scheme as given, then fall back to a lowercased lookup. The registered wrappers are not themselves lowercased, so a wrapper registered as `MyStream` remains unreachable as `mystream`, as in PHP. Fixes #65870. --- src/wp-includes/functions.php | 11 +++++++++-- tests/phpunit/tests/functions.php | 22 ++++++++++++++++++++++ 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/src/wp-includes/functions.php b/src/wp-includes/functions.php index f5002a45de1e8..e386a23ccf12d 100644 --- a/src/wp-includes/functions.php +++ b/src/wp-includes/functions.php @@ -7494,6 +7494,7 @@ function _device_can_upload() { * Tests if a given path is a stream URL * * @since 3.5.0 + * @since 7.2.0 A lowercased scheme is accepted as a fallback, matching PHP. * * @param string $path The resource path or URL. * @return bool True if the path is a stream URL. @@ -7506,9 +7507,15 @@ function wp_is_stream( $path ) { return false; } - $stream = substr( $path, 0, $scheme_separator ); + $stream = substr( $path, 0, $scheme_separator ); + $wrappers = stream_get_wrappers(); - return in_array( $stream, stream_get_wrappers(), true ); + /* + * PHP looks the scheme up as given, then retries once with it lowercased. + * This is not a case-insensitive match: a wrapper registered as `MyStream` + * is not reachable as `mystream`. + */ + return in_array( $stream, $wrappers, true ) || in_array( strtolower( $stream ), $wrappers, true ); } /** diff --git a/tests/phpunit/tests/functions.php b/tests/phpunit/tests/functions.php index 19c721aeaa46a..9db34df8f4a87 100644 --- a/tests/phpunit/tests/functions.php +++ b/tests/phpunit/tests/functions.php @@ -2007,6 +2007,8 @@ public function data_validate_file() { /** * Test stream URL validation. * + * @ticket 65870 + * * @dataProvider data_wp_is_stream * * @param string $path The resource path or URL. @@ -2037,6 +2039,7 @@ public function data_wp_is_stream() { array( 'https://example.com', true ), array( 'ftp://example.com', true ), array( 'file:///path/to/some/file', true ), + array( 'FILE:///path/to/some/file', true ), array( 'php://some/php/file.php', true ), // Non-stream examples. @@ -2047,6 +2050,25 @@ public function data_wp_is_stream() { ); } + /** + * Tests that a wrapper registered under a mixed case scheme is not + * reachable under a lowercased scheme. + * + * @ticket 65870 + */ + public function test_wp_is_stream_does_not_lowercase_registered_wrappers() { + require_once DIR_TESTROOT . '/includes/class-wp-test-stream.php'; + stream_wrapper_register( 'wpTestMixedCase', 'WP_Test_Stream' ); + + $exact = wp_is_stream( 'wpTestMixedCase://foo' ); + $lowercased = wp_is_stream( 'wptestmixedcase://foo' ); + + stream_wrapper_unregister( 'wpTestMixedCase' ); + + $this->assertTrue( $exact, 'The scheme was not matched as registered.' ); + $this->assertFalse( $lowercased, 'A lowercased scheme matched a mixed case wrapper.' ); + } + /** * Test human_readable_duration(). *