From cfd713aad908d8b32b09c027a151725761a9940d Mon Sep 17 00:00:00 2001 From: Khokan Sardar Date: Thu, 13 Aug 2026 19:02:17 +0530 Subject: [PATCH 1/5] 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(). * From 9577a8f465c7beae41bde8838426b266c06ae553 Mon Sep 17 00:00:00 2001 From: Jon Surrell Date: Tue, 8 Sep 2026 07:53:20 +0400 Subject: [PATCH 2/5] General: Match PHP stream URL recognition with differential tests. Compare wp_is_stream() with invocation of a registered PHP wrapper. Reject one-character schemes and recognize the case-sensitive data: prefix without slashes. Cover case matching, scheme syntax, and wrapper registration state. Confirmed red/green failures for one-character schemes and data: URLs. See #65870. --- src/wp-includes/functions.php | 14 +++-- tests/phpunit/tests/functions.php | 100 +++++++++++++++++++++++++++--- 2 files changed, 100 insertions(+), 14 deletions(-) diff --git a/src/wp-includes/functions.php b/src/wp-includes/functions.php index 71a24f2013004..a99734f88a8a3 100644 --- a/src/wp-includes/functions.php +++ b/src/wp-includes/functions.php @@ -7543,19 +7543,25 @@ function _device_can_upload() { } /** - * Tests if a given path is a stream URL + * 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. + * @since 7.2.0 Matches PHP's scheme case fallback, minimum scheme length, + * and recognition of data: URLs without slashes. * * @param string $path The resource path or URL. * @return bool True if the path is a stream URL. */ function wp_is_stream( $path ) { + // PHP also recognizes the case-sensitive "data:" prefix without slashes. + if ( str_starts_with( $path, 'data:' ) ) { + return in_array( 'data', stream_get_wrappers(), true ); + } + $scheme_separator = strpos( $path, '://' ); - if ( false === $scheme_separator ) { - // $path isn't a stream. + if ( false === $scheme_separator || $scheme_separator < 2 ) { + // PHP requires at least two characters in a scheme. return false; } diff --git a/tests/phpunit/tests/functions.php b/tests/phpunit/tests/functions.php index a4160b79cf341..6db232baed81e 100644 --- a/tests/phpunit/tests/functions.php +++ b/tests/phpunit/tests/functions.php @@ -2165,22 +2165,102 @@ public function data_wp_is_stream() { } /** - * Tests that a wrapper registered under a mixed case scheme is not - * reachable under a lowercased scheme. + * Tests stream URL recognition against PHP's wrapper selection. * * @ticket 65870 + * + * @dataProvider data_wp_is_stream_matches_php + * + * @param string $scheme The registered scheme. + * @param string $path The path to open. + * @param bool $expected Whether PHP should select the registered wrapper. + * @param bool $register Whether to register the wrapper. Default true. */ - 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' ); + public function test_wp_is_stream_matches_php( $scheme, $path, $expected, $register = true ) { + $wrapper = new class() { + public $context; + public static $invoked = false; + + public function stream_open( $path, $mode, $options, &$opened_path ) { + self::$invoked = true; + return false; + } + }; - $exact = wp_is_stream( 'wpTestMixedCase://foo' ); - $lowercased = wp_is_stream( 'wptestmixedcase://foo' ); + $wrapper::$invoked = false; + $restore_data = 'data' === $scheme; + $registered = false; + $handle = false; + + if ( $restore_data ) { + $this->assertTrue( stream_wrapper_unregister( 'data' ) ); + } - stream_wrapper_unregister( 'wpTestMixedCase' ); + try { + if ( $register ) { + $registered = stream_wrapper_register( $scheme, get_class( $wrapper ) ); + $this->assertTrue( $registered ); + } + + // Record selection without requiring the wrapper to parse or open the URL. + $handle = @fopen( $path, 'r' ); + + $this->assertSame( $expected, $wrapper::$invoked, 'PHP did not select the expected wrapper.' ); + $this->assertSame( $wrapper::$invoked, wp_is_stream( $path ), 'WordPress and PHP disagree on stream URL recognition.' ); + } finally { + if ( is_resource( $handle ) ) { + fclose( $handle ); + } + if ( $registered ) { + stream_wrapper_unregister( $scheme ); + } + if ( $restore_data ) { + stream_wrapper_restore( 'data' ); + } + } + } - $this->assertTrue( $exact, 'The scheme was not matched as registered.' ); - $this->assertFalse( $lowercased, 'A lowercased scheme matched a mixed case wrapper.' ); + /** + * Data provider for stream URL recognition against PHP. + * + * @return array[] + */ + public function data_wp_is_stream_matches_php() { + return array( + 'lowercase scheme' => array( 'wpteststream', 'wpteststream://bucket/file', true ), + 'lowercase registration mixed case' => array( 'wpteststream', 'wpTestStream://bucket/file', true ), + 'lowercase registration uppercase' => array( 'wpteststream', 'WPTESTSTREAM://bucket/file', true ), + 'mixed case exact match' => array( 'wpTestStream', 'wpTestStream://bucket/file', true ), + 'mixed case lowercased' => array( 'wpTestStream', 'wpteststream://bucket/file', false ), + 'mixed case uppercased' => array( 'wpTestStream', 'WPTESTSTREAM://bucket/file', false ), + 'mixed case with different casing' => array( 'wpTestStream', 'wptestSTREAM://bucket/file', false ), + 'uppercase exact match' => array( 'WPTESTSTREAM', 'WPTESTSTREAM://bucket/file', true ), + 'uppercase lowercased' => array( 'WPTESTSTREAM', 'wpteststream://bucket/file', false ), + 'one-character scheme' => array( 'w', 'w://bucket/file', false ), + 'one-character digit scheme' => array( '1', '1://bucket/file', false ), + 'two-character scheme' => array( 'wp', 'wp://bucket/file', true ), + 'numeric scheme' => array( '12', '12://bucket/file', true ), + 'punctuation scheme' => array( '+-.', '+-.://bucket/file', true ), + 'plus in scheme' => array( 'wptest+stream', 'wptest+stream://bucket/file', true ), + 'hyphen in scheme' => array( 'wptest-stream', 'wptest-stream://bucket/file', true ), + 'dot in scheme' => array( 'wptest.stream', 'wptest.stream://bucket/file', true ), + 'unknown scheme' => array( 'wpteststream', 'wptestunknown://bucket/file', false ), + 'unregistered scheme' => array( 'wpteststream', 'wpteststream://bucket/file', false, false ), + 'no slashes' => array( 'wpteststream', 'wpteststream:bucket/file', false ), + 'one slash' => array( 'wpteststream', 'wpteststream:/bucket/file', false ), + 'leading space' => array( 'wpteststream', ' wpteststream://bucket/file', false ), + 'leading path' => array( 'wpteststream', '/wpteststream://bucket/file', false ), + 'existing local file' => array( 'wpteststream', __FILE__, false ), + 'data without slashes' => array( 'data', 'data:text/plain,hello', true ), + 'data with one slash' => array( 'data', 'data:/text/plain,hello', true ), + 'data with two slashes' => array( 'data', 'data://text/plain,hello', true ), + 'data uppercase without slashes' => array( 'data', 'DATA:text/plain,hello', false ), + 'data mixed case without slashes' => array( 'data', 'Data:text/plain,hello', false ), + 'data uppercase with slashes' => array( 'data', 'DATA://text/plain,hello', true ), + 'data with URL in payload' => array( 'data', 'data:text/plain,wpteststream://bucket/file', true ), + 'data with empty payload' => array( 'data', 'data:', true ), + 'data unregistered' => array( 'data', 'data:text/plain,hello', false, false ), + ); } /** From b091b8122ae27bd566b73fc4a3cbd88a416b508f Mon Sep 17 00:00:00 2001 From: Jon Surrell Date: Tue, 8 Sep 2026 07:54:23 +0400 Subject: [PATCH 3/5] Tests: Name the wp_is_stream() datasets. Replace numeric dataset keys with descriptions of the URL or path so test output identifies each case. --- tests/phpunit/tests/functions.php | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/tests/phpunit/tests/functions.php b/tests/phpunit/tests/functions.php index 6db232baed81e..58864584494c2 100644 --- a/tests/phpunit/tests/functions.php +++ b/tests/phpunit/tests/functions.php @@ -2149,18 +2149,18 @@ public function test_wp_is_stream( $path, $expected ) { public function data_wp_is_stream() { return array( // Legitimate stream examples. - array( 'http://example.com', true ), - 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 ), + 'HTTP URL' => array( 'http://example.com', true ), + 'HTTPS URL' => array( 'https://example.com', true ), + 'FTP URL' => array( 'ftp://example.com', true ), + 'file URL' => array( 'file:///path/to/some/file', true ), + 'uppercase file scheme' => array( 'FILE:///path/to/some/file', true ), + 'PHP stream URL' => array( 'php://some/php/file.php', true ), // Non-stream examples. - array( 'fakestream://foo/bar/baz', false ), - array( '../../some/relative/path', false ), - array( 'some/other/relative/path', false ), - array( '/leading/relative/path', false ), + 'unregistered stream scheme' => array( 'fakestream://foo/bar/baz', false ), + 'parent-relative path' => array( '../../some/relative/path', false ), + 'relative path' => array( 'some/other/relative/path', false ), + 'absolute path' => array( '/leading/relative/path', false ), ); } From 3104663d98bd0b32409e97285bd645f54ba05f17 Mon Sep 17 00:00:00 2001 From: Jon Surrell Date: Tue, 8 Sep 2026 08:24:02 +0400 Subject: [PATCH 4/5] General: Exclude slashless data URLs and test built-in handlers. Keep wp_is_stream() limited to schemes followed by ://, since callers depend on that delimiter. Document the distinction between wrapper selection and opening a resource. Test built-in data handler case variants separately from replacement-wrapper selection, with PHP and file controls and allow_url_fopen handling. Confirmed the exclusion test fails before removing the special case and passes afterward. See #65870. --- src/wp-includes/functions.php | 12 ++--- tests/phpunit/tests/functions.php | 85 +++++++++++++++++++++++++------ 2 files changed, 75 insertions(+), 22 deletions(-) diff --git a/src/wp-includes/functions.php b/src/wp-includes/functions.php index a99734f88a8a3..e7ec926ab6994 100644 --- a/src/wp-includes/functions.php +++ b/src/wp-includes/functions.php @@ -7545,19 +7545,17 @@ function _device_can_upload() { /** * Tests if a given path is a stream URL. * + * Requires the scheme to be followed by `://`. PHP also supports `data:` URLs + * without `//`, but these are excluded because callers rely on this delimiter. + * Does not determine whether the wrapper can open the resource. + * * @since 3.5.0 - * @since 7.2.0 Matches PHP's scheme case fallback, minimum scheme length, - * and recognition of data: URLs without slashes. + * @since 7.2.0 Matches PHP's scheme case fallback and minimum scheme length. * * @param string $path The resource path or URL. * @return bool True if the path is a stream URL. */ function wp_is_stream( $path ) { - // PHP also recognizes the case-sensitive "data:" prefix without slashes. - if ( str_starts_with( $path, 'data:' ) ) { - return in_array( 'data', stream_get_wrappers(), true ); - } - $scheme_separator = strpos( $path, '://' ); if ( false === $scheme_separator || $scheme_separator < 2 ) { diff --git a/tests/phpunit/tests/functions.php b/tests/phpunit/tests/functions.php index 58864584494c2..8618a29aa4fe6 100644 --- a/tests/phpunit/tests/functions.php +++ b/tests/phpunit/tests/functions.php @@ -2149,18 +2149,22 @@ public function test_wp_is_stream( $path, $expected ) { public function data_wp_is_stream() { return array( // Legitimate stream examples. - 'HTTP URL' => array( 'http://example.com', true ), - 'HTTPS URL' => array( 'https://example.com', true ), - 'FTP URL' => array( 'ftp://example.com', true ), - 'file URL' => array( 'file:///path/to/some/file', true ), - 'uppercase file scheme' => array( 'FILE:///path/to/some/file', true ), - 'PHP stream URL' => array( 'php://some/php/file.php', true ), + 'HTTP URL' => array( 'http://example.com', true ), + 'HTTPS URL' => array( 'https://example.com', true ), + 'FTP URL' => array( 'ftp://example.com', true ), + 'file URL' => array( 'file:///path/to/some/file', true ), + 'uppercase file scheme' => array( 'FILE:///path/to/some/file', true ), + 'PHP stream URL' => array( 'php://some/php/file.php', true ), // Non-stream examples. - 'unregistered stream scheme' => array( 'fakestream://foo/bar/baz', false ), - 'parent-relative path' => array( '../../some/relative/path', false ), - 'relative path' => array( 'some/other/relative/path', false ), - 'absolute path' => array( '/leading/relative/path', false ), + 'unregistered stream scheme' => array( 'fakestream://foo/bar/baz', false ), + 'parent-relative path' => array( '../../some/relative/path', false ), + 'relative path' => array( 'some/other/relative/path', false ), + 'absolute path' => array( '/leading/relative/path', false ), + 'data URL without slashes' => array( 'data:text/plain,hello', false ), + 'data URL with one slash' => array( 'data:/text/plain,hello', false ), + 'data URL with URL payload' => array( 'data:text/plain,http://example.com', false ), + 'data URL with empty payload' => array( 'data:', false ), ); } @@ -2251,15 +2255,66 @@ public function data_wp_is_stream_matches_php() { 'leading space' => array( 'wpteststream', ' wpteststream://bucket/file', false ), 'leading path' => array( 'wpteststream', '/wpteststream://bucket/file', false ), 'existing local file' => array( 'wpteststream', __FILE__, false ), - 'data without slashes' => array( 'data', 'data:text/plain,hello', true ), - 'data with one slash' => array( 'data', 'data:/text/plain,hello', true ), 'data with two slashes' => array( 'data', 'data://text/plain,hello', true ), 'data uppercase without slashes' => array( 'data', 'DATA:text/plain,hello', false ), 'data mixed case without slashes' => array( 'data', 'Data:text/plain,hello', false ), 'data uppercase with slashes' => array( 'data', 'DATA://text/plain,hello', true ), - 'data with URL in payload' => array( 'data', 'data:text/plain,wpteststream://bucket/file', true ), - 'data with empty payload' => array( 'data', 'data:', true ), - 'data unregistered' => array( 'data', 'data:text/plain,hello', false, false ), + 'data mixed case with slashes' => array( 'data', 'Data://text/plain,hello', true ), + 'data unregistered' => array( 'data', 'data://text/plain,hello', false, false ), + ); + } + + /** + * Tests built-in handlers separately from wrapper selection. + * + * @ticket 65870 + * + * @dataProvider data_php_builtin_stream_wrapper_case + * + * @param string $url The URL to open. + * @param string|false $expected Expected contents, or false if opening fails. + */ + public function test_php_builtin_stream_wrapper_case( $url, $expected ) { + if ( 0 === strncasecmp( $url, 'data:', 5 ) && ! ini_get( 'allow_url_fopen' ) ) { + $this->markTestSkipped( 'The data wrapper requires allow_url_fopen.' ); + } + + $handle = false; + try { + $handle = @fopen( $url, 'r' ); + $this->assertSame( false !== $expected, is_resource( $handle ), 'Unexpected built-in handler open result.' ); + if ( is_resource( $handle ) ) { + $this->assertSame( $expected, stream_get_contents( $handle ) ); + } + } finally { + if ( is_resource( $handle ) ) { + fclose( $handle ); + } + } + } + + /** + * Data provider for built-in handler behavior with different scheme cases. + * + * @return array[] + */ + public function data_php_builtin_stream_wrapper_case() { + $file = DIR_TESTDATA . '/formatting/entities.txt'; + $contents = file_get_contents( $file ); + + return array( + 'data lowercase without slashes' => array( 'data:text/plain,hello', 'hello' ), + 'data uppercase without slashes' => array( 'DATA:text/plain,hello', false ), + 'data mixed case without slashes' => array( 'Data:text/plain,hello', false ), + 'data lowercase with slashes' => array( 'data://text/plain,hello', 'hello' ), + 'data uppercase with slashes' => array( 'DATA://text/plain,hello', false ), + 'data mixed case with slashes' => array( 'Data://text/plain,hello', false ), + 'php lowercase' => array( 'php://memory', '' ), + 'php uppercase' => array( 'PHP://memory', '' ), + 'php mixed case' => array( 'Php://memory', '' ), + 'file lowercase' => array( 'file://' . $file, $contents ), + 'file uppercase' => array( 'FILE://' . $file, $contents ), + 'file mixed case' => array( 'File://' . $file, $contents ), ); } From a01734d95927e34a8ce712b5bf67dc762a799568 Mon Sep 17 00:00:00 2001 From: Jon Surrell Date: Tue, 8 Sep 2026 09:05:14 +0400 Subject: [PATCH 5/5] General: Limit the stream matching change to scheme case. Keep the lowercase fallback and direct PHP case-comparison tests. Defer scheme-length changes, data URL recognition, built-in handler tests, and caller work to the dependent follow-up. The 19 stream tests fail in three cases without case fallback and pass with it. The full functions test file passes: 240 tests, 967 assertions, one skip. --- src/wp-includes/functions.php | 10 +-- tests/phpunit/tests/functions.php | 126 +++++------------------------- 2 files changed, 21 insertions(+), 115 deletions(-) diff --git a/src/wp-includes/functions.php b/src/wp-includes/functions.php index e7ec926ab6994..14cc9546528e6 100644 --- a/src/wp-includes/functions.php +++ b/src/wp-includes/functions.php @@ -7545,12 +7545,8 @@ function _device_can_upload() { /** * Tests if a given path is a stream URL. * - * Requires the scheme to be followed by `://`. PHP also supports `data:` URLs - * without `//`, but these are excluded because callers rely on this delimiter. - * Does not determine whether the wrapper can open the resource. - * * @since 3.5.0 - * @since 7.2.0 Matches PHP's scheme case fallback and minimum scheme length. + * @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. @@ -7558,8 +7554,8 @@ function _device_can_upload() { function wp_is_stream( $path ) { $scheme_separator = strpos( $path, '://' ); - if ( false === $scheme_separator || $scheme_separator < 2 ) { - // PHP requires at least two characters in a scheme. + if ( false === $scheme_separator ) { + // $path isn't a stream. return false; } diff --git a/tests/phpunit/tests/functions.php b/tests/phpunit/tests/functions.php index 8618a29aa4fe6..5b5660df2439a 100644 --- a/tests/phpunit/tests/functions.php +++ b/tests/phpunit/tests/functions.php @@ -2149,38 +2149,33 @@ public function test_wp_is_stream( $path, $expected ) { public function data_wp_is_stream() { return array( // Legitimate stream examples. - 'HTTP URL' => array( 'http://example.com', true ), - 'HTTPS URL' => array( 'https://example.com', true ), - 'FTP URL' => array( 'ftp://example.com', true ), - 'file URL' => array( 'file:///path/to/some/file', true ), - 'uppercase file scheme' => array( 'FILE:///path/to/some/file', true ), - 'PHP stream URL' => array( 'php://some/php/file.php', true ), + 'HTTP URL' => array( 'http://example.com', true ), + 'HTTPS URL' => array( 'https://example.com', true ), + 'FTP URL' => array( 'ftp://example.com', true ), + 'file URL' => array( 'file:///path/to/some/file', true ), + 'uppercase file scheme' => array( 'FILE:///path/to/some/file', true ), + 'PHP stream URL' => array( 'php://some/php/file.php', true ), // Non-stream examples. - 'unregistered stream scheme' => array( 'fakestream://foo/bar/baz', false ), - 'parent-relative path' => array( '../../some/relative/path', false ), - 'relative path' => array( 'some/other/relative/path', false ), - 'absolute path' => array( '/leading/relative/path', false ), - 'data URL without slashes' => array( 'data:text/plain,hello', false ), - 'data URL with one slash' => array( 'data:/text/plain,hello', false ), - 'data URL with URL payload' => array( 'data:text/plain,http://example.com', false ), - 'data URL with empty payload' => array( 'data:', false ), + 'unregistered stream scheme' => array( 'fakestream://foo/bar/baz', false ), + 'parent-relative path' => array( '../../some/relative/path', false ), + 'relative path' => array( 'some/other/relative/path', false ), + 'absolute path' => array( '/leading/relative/path', false ), ); } /** - * Tests stream URL recognition against PHP's wrapper selection. + * Tests scheme case matching against PHP's wrapper selection. * * @ticket 65870 * - * @dataProvider data_wp_is_stream_matches_php + * @dataProvider data_wp_is_stream_matches_php_case_matching * * @param string $scheme The registered scheme. * @param string $path The path to open. * @param bool $expected Whether PHP should select the registered wrapper. - * @param bool $register Whether to register the wrapper. Default true. */ - public function test_wp_is_stream_matches_php( $scheme, $path, $expected, $register = true ) { + public function test_wp_is_stream_matches_php_case_matching( $scheme, $path, $expected ) { $wrapper = new class() { public $context; public static $invoked = false; @@ -2192,25 +2187,18 @@ public function stream_open( $path, $mode, $options, &$opened_path ) { }; $wrapper::$invoked = false; - $restore_data = 'data' === $scheme; $registered = false; $handle = false; - if ( $restore_data ) { - $this->assertTrue( stream_wrapper_unregister( 'data' ) ); - } - try { - if ( $register ) { - $registered = stream_wrapper_register( $scheme, get_class( $wrapper ) ); - $this->assertTrue( $registered ); - } + $registered = stream_wrapper_register( $scheme, get_class( $wrapper ) ); + $this->assertTrue( $registered ); // Record selection without requiring the wrapper to parse or open the URL. $handle = @fopen( $path, 'r' ); $this->assertSame( $expected, $wrapper::$invoked, 'PHP did not select the expected wrapper.' ); - $this->assertSame( $wrapper::$invoked, wp_is_stream( $path ), 'WordPress and PHP disagree on stream URL recognition.' ); + $this->assertSame( $wrapper::$invoked, wp_is_stream( $path ), 'WordPress and PHP disagree on scheme case matching.' ); } finally { if ( is_resource( $handle ) ) { fclose( $handle ); @@ -2218,18 +2206,15 @@ public function stream_open( $path, $mode, $options, &$opened_path ) { if ( $registered ) { stream_wrapper_unregister( $scheme ); } - if ( $restore_data ) { - stream_wrapper_restore( 'data' ); - } } } /** - * Data provider for stream URL recognition against PHP. + * Data provider for scheme case matching against PHP. * * @return array[] */ - public function data_wp_is_stream_matches_php() { + public function data_wp_is_stream_matches_php_case_matching() { return array( 'lowercase scheme' => array( 'wpteststream', 'wpteststream://bucket/file', true ), 'lowercase registration mixed case' => array( 'wpteststream', 'wpTestStream://bucket/file', true ), @@ -2240,81 +2225,6 @@ public function data_wp_is_stream_matches_php() { 'mixed case with different casing' => array( 'wpTestStream', 'wptestSTREAM://bucket/file', false ), 'uppercase exact match' => array( 'WPTESTSTREAM', 'WPTESTSTREAM://bucket/file', true ), 'uppercase lowercased' => array( 'WPTESTSTREAM', 'wpteststream://bucket/file', false ), - 'one-character scheme' => array( 'w', 'w://bucket/file', false ), - 'one-character digit scheme' => array( '1', '1://bucket/file', false ), - 'two-character scheme' => array( 'wp', 'wp://bucket/file', true ), - 'numeric scheme' => array( '12', '12://bucket/file', true ), - 'punctuation scheme' => array( '+-.', '+-.://bucket/file', true ), - 'plus in scheme' => array( 'wptest+stream', 'wptest+stream://bucket/file', true ), - 'hyphen in scheme' => array( 'wptest-stream', 'wptest-stream://bucket/file', true ), - 'dot in scheme' => array( 'wptest.stream', 'wptest.stream://bucket/file', true ), - 'unknown scheme' => array( 'wpteststream', 'wptestunknown://bucket/file', false ), - 'unregistered scheme' => array( 'wpteststream', 'wpteststream://bucket/file', false, false ), - 'no slashes' => array( 'wpteststream', 'wpteststream:bucket/file', false ), - 'one slash' => array( 'wpteststream', 'wpteststream:/bucket/file', false ), - 'leading space' => array( 'wpteststream', ' wpteststream://bucket/file', false ), - 'leading path' => array( 'wpteststream', '/wpteststream://bucket/file', false ), - 'existing local file' => array( 'wpteststream', __FILE__, false ), - 'data with two slashes' => array( 'data', 'data://text/plain,hello', true ), - 'data uppercase without slashes' => array( 'data', 'DATA:text/plain,hello', false ), - 'data mixed case without slashes' => array( 'data', 'Data:text/plain,hello', false ), - 'data uppercase with slashes' => array( 'data', 'DATA://text/plain,hello', true ), - 'data mixed case with slashes' => array( 'data', 'Data://text/plain,hello', true ), - 'data unregistered' => array( 'data', 'data://text/plain,hello', false, false ), - ); - } - - /** - * Tests built-in handlers separately from wrapper selection. - * - * @ticket 65870 - * - * @dataProvider data_php_builtin_stream_wrapper_case - * - * @param string $url The URL to open. - * @param string|false $expected Expected contents, or false if opening fails. - */ - public function test_php_builtin_stream_wrapper_case( $url, $expected ) { - if ( 0 === strncasecmp( $url, 'data:', 5 ) && ! ini_get( 'allow_url_fopen' ) ) { - $this->markTestSkipped( 'The data wrapper requires allow_url_fopen.' ); - } - - $handle = false; - try { - $handle = @fopen( $url, 'r' ); - $this->assertSame( false !== $expected, is_resource( $handle ), 'Unexpected built-in handler open result.' ); - if ( is_resource( $handle ) ) { - $this->assertSame( $expected, stream_get_contents( $handle ) ); - } - } finally { - if ( is_resource( $handle ) ) { - fclose( $handle ); - } - } - } - - /** - * Data provider for built-in handler behavior with different scheme cases. - * - * @return array[] - */ - public function data_php_builtin_stream_wrapper_case() { - $file = DIR_TESTDATA . '/formatting/entities.txt'; - $contents = file_get_contents( $file ); - - return array( - 'data lowercase without slashes' => array( 'data:text/plain,hello', 'hello' ), - 'data uppercase without slashes' => array( 'DATA:text/plain,hello', false ), - 'data mixed case without slashes' => array( 'Data:text/plain,hello', false ), - 'data lowercase with slashes' => array( 'data://text/plain,hello', 'hello' ), - 'data uppercase with slashes' => array( 'DATA://text/plain,hello', false ), - 'data mixed case with slashes' => array( 'Data://text/plain,hello', false ), - 'php lowercase' => array( 'php://memory', '' ), - 'php uppercase' => array( 'PHP://memory', '' ), - 'php mixed case' => array( 'Php://memory', '' ), - 'file lowercase' => array( 'file://' . $file, $contents ), - 'file uppercase' => array( 'FILE://' . $file, $contents ), - 'file mixed case' => array( 'File://' . $file, $contents ), ); }