Ticket #36356: 36356.diff
File 36356.diff, 13.5 KB (added by , 8 years ago) |
---|
-
src/wp-includes/http.php
diff --git src/wp-includes/http.php src/wp-includes/http.php index 3738b79..1d18bc8 100644
function ms_allowed_http_request_hosts( $is_external, $host ) { 623 623 } 624 624 625 625 /** 626 * A wrapper for PHP's parse_url() function that handles edgecases in < PHP 5.4.7 626 * A wrapper for PHP's parse_url() function that handles consistency in the return 627 * values across PHP versions. 627 628 * 628 629 * PHP 5.4.7 expanded parse_url()'s ability to handle non-absolute url's, including 629 * schemeless and relative url's with :// in the path, this works around those 630 * limitations providing a standard output on PHP 5.2~5.4+. 630 * schemeless and relative url's with :// in the path. This function works around 631 * those limitations providing a standard output on PHP 5.2~5.4+. 632 * 633 * Secondly, across various PHP versions, schemeless URLs starting containing a ":" 634 * in the query are being handled inconsistently. This function works around those 635 * differences as well. 636 * 637 * Lastly, PHP does not recognize query parameters which start with an ampersant (&) 638 * instead of a question mark (?) as query parameters. That edge case is also 639 * handled by this function. 631 640 * 632 641 * Error suppression is used as prior to PHP 5.3.3, an E_WARNING would be generated 633 642 * when URL parsing failed. 634 643 * 635 644 * @since 4.4.0 636 645 * @since 4.7.0 The $component parameter was added for parity with PHP's parse_url(). 646 * Handling of query parameters starting with & was added. 637 647 * 638 648 * @param string $url The URL to parse. 639 649 * @param int $component The specific component to retrieve. Use one of the PHP 640 650 * predefined constants to specify which one. 641 651 * Defaults to -1 (= return all parts as an array). 642 652 * @see http://php.net/manual/en/function.parse-url.php 643 * @return mixed False on failure; Array of URL components on success;644 * When a specific component has been requested: null if the component doesn't645 * exist in the given URL; a sting or - in the case of PHP_URL_PORT - integer646 * when it does;See parse_url()'s return values.653 * @return mixed False on parse failure; Array of URL components on success; 654 * When a specific component has been requested: null if the component 655 * doesn't exist in the given URL; a sting or - in the case of 656 * PHP_URL_PORT - integer when it does. See parse_url()'s return values. 647 657 */ 648 658 function wp_parse_url( $url, $component = -1 ) { 649 $parts = @parse_url( $url , $component);659 $parts = @parse_url( $url ); 650 660 651 if ( version_compare( PHP_VERSION, '5.4.7', '>=' ) ) { 652 return $parts; 653 } 661 $schemeless_with_colon = ( '//' === substr( $url, 0, 2 ) && false !== strpos( $url, ':' ) ); 654 662 655 663 if ( false === $parts ) { 664 $url = strval( $url ); 665 656 666 // < PHP 5.4.7 compat, trouble with relative paths including a scheme break in the path. 657 if ( '/' == $url[0] && false !== strpos( $url, '://' ) ) { 658 if ( in_array( $component, array( PHP_URL_SCHEME, PHP_URL_HOST ), true ) ) { 659 return null; 667 if ( '' !== $url && '/' === $url[0] && ( false !== strpos( $url, '://' ) || true === $schemeless_with_colon ) ) { 668 if ( true === $schemeless_with_colon ) { 669 if ( PHP_URL_SCHEME === $component ) { 670 return null; 671 } else { 672 // Since we know it's a schemeless path, prefix with a scheme placeholder. 673 $url = 'placeholder:' . $url; 674 $to_unset = array( 'scheme' ); 675 } 676 } else { 677 if ( in_array( $component, array( PHP_URL_SCHEME, PHP_URL_HOST ), true ) ) { 678 return null; 679 } else { 680 // Since we know it's a relative path, prefix with a scheme/host placeholder. 681 $url = 'placeholder://placeholder' . $url; 682 $to_unset = array( 'scheme', 'host' ); 683 } 660 684 } 661 // Since we know it's a relative path, prefix with a scheme/host placeholder and try again. 662 if ( ! $parts = @parse_url( 'placeholder://placeholder' . $url, $component ) ) { 663 return $parts; 685 686 $parts = @parse_url( $url ); 687 if ( false === $parts ) { 688 return _get_component_from_parsed_url_array( $parts, $component ); 664 689 } 690 665 691 // Remove the placeholder values. 666 if ( -1 === $component) {667 unset( $parts[ 'scheme'], $parts['host'] );692 foreach ( $to_unset as $key ) { 693 unset( $parts[ $key ] ); 668 694 } 669 695 } else { 670 return $parts;696 return _get_component_from_parsed_url_array( $parts, $component ); 671 697 } 672 698 } 673 699 674 700 // < PHP 5.4.7 compat, doesn't detect a schemeless URL's host field. 675 if ( '//' == substr( $url, 0, 2 ) ) { 676 if ( -1 === $component && ! isset( $parts['host'] ) ) { 677 $path_parts = explode( '/', substr( $parts['path'], 2 ), 2 ); 678 $parts['host'] = $path_parts[0]; 679 if ( isset( $path_parts[1] ) ) { 680 $parts['path'] = '/' . $path_parts[1]; 681 } else { 682 unset( $parts['path'] ); 683 } 684 } elseif ( PHP_URL_HOST === $component || PHP_URL_PATH === $component ) { 685 $all_parts = @parse_url( $url ); 686 if ( ! isset( $all_parts['host'] ) ) { 687 $path_parts = explode( '/', substr( $all_parts['path'], 2 ), 2 ); 688 if ( PHP_URL_PATH === $component ) { 689 if ( isset( $path_parts[1] ) ) { 690 $parts = '/' . $path_parts[1]; 691 } else { 692 $parts = null; 693 } 694 } elseif ( PHP_URL_HOST === $component ) { 695 $parts = $path_parts[0]; 696 } 697 } 701 if ( '//' == substr( $url, 0, 2 ) && ! isset( $parts['host'] ) && isset( $parts['path'] ) ) { 702 $path_parts = explode( '/', substr( $parts['path'], 2 ), 2 ); 703 $parts['host'] = $path_parts[0]; 704 if ( isset( $path_parts[1] ) ) { 705 $parts['path'] = '/' . $path_parts[1]; 706 } else { 707 unset( $parts['path'] ); 708 } 709 } 710 711 // Deal with query parameters starting with & instead of ? 712 if ( isset( $parts['path'] ) && ! isset( $parts['query'] ) ) { 713 $ampersant_in_path = strpos( $parts['path'], '&', strrpos( $parts['path'], '/' ) ); 714 if ( false !== $ampersant_in_path ) { 715 $parts['query'] = substr( $parts['path'], ( $ampersant_in_path + 1 ) ); 716 $parts['path'] = str_replace( '&' . $parts['query'], '', $parts['path'] ); 698 717 } 699 718 } 700 719 701 return $parts; 720 // HHVM mistakenly interprets a ':400' in the query string as the port. 721 if ( isset( $parts['port'], $parts['query'] ) && false !== strpos( $parts['query'], ':' . $parts['port'] ) ) { 722 if ( 1 === substr_count( $url, ':' . $parts['port'] ) ) { 723 unset( $parts['port'] ); 724 } 725 } 726 727 return _get_component_from_parsed_url_array( $parts, $component ); 728 } 729 730 /** 731 * Retrieve a specific component from a parsed URL array. 732 * 733 * @internal 734 * 735 * @since 4.7.0 736 * 737 * @param array|false $url_parts The parsed URL. Can be false if the URL failed to parse. 738 * @param int $component The specific component to retrieve. Use one of the PHP 739 * predefined constants to specify which one. 740 * Defaults to -1 (= return all parts as an array). 741 * @see http://php.net/manual/en/function.parse-url.php 742 * @return mixed False on parse failure; Array of URL components on success; 743 * When a specific component has been requested: null if the component 744 * doesn't exist in the given URL; a sting or - in the case of 745 * PHP_URL_PORT - integer when it does. See parse_url()'s return values. 746 */ 747 function _get_component_from_parsed_url_array( $url_parts, $component = -1 ) { 748 if ( -1 === $component ) { 749 return $url_parts; 750 } 751 752 $key = _wp_translate_php_url_constant_to_key( $component ); 753 if ( false !== $key && is_array( $url_parts ) && isset( $url_parts[ $key ] ) ) { 754 return $url_parts[ $key ]; 755 } else { 756 return null; 757 } 758 } 759 760 /** 761 * Translate a PHP_URL_* constant to the named array keys PHP uses. 762 * 763 * @internal 764 * 765 * @since 4.7.0 766 * 767 * @see http://php.net/manual/en/url.constants.php 768 * 769 * @param int $constant PHP_URL_* constant. 770 * @return string|bool The named key or false. 771 */ 772 function _wp_translate_php_url_constant_to_key( $constant ) { 773 $translation = array( 774 PHP_URL_SCHEME => 'scheme', 775 PHP_URL_HOST => 'host', 776 PHP_URL_PORT => 'port', 777 PHP_URL_USER => 'user', 778 PHP_URL_PASS => 'pass', 779 PHP_URL_PATH => 'path', 780 PHP_URL_QUERY => 'query', 781 PHP_URL_FRAGMENT => 'fragment', 782 ); 783 784 if ( isset( $translation[ $constant ] ) ) { 785 return $translation[ $constant ]; 786 } else { 787 return false; 788 } 702 789 } -
tests/phpunit/tests/http/http.php
diff --git tests/phpunit/tests/http/http.php tests/phpunit/tests/http/http.php index 607eb47..a3e80e5 100644
class Tests_HTTP_HTTP extends WP_UnitTestCase { 107 107 // PHP's parse_url() calls this an invalid url, we handle it as a path 108 108 array( '/://example.com/', array( 'path' => '/://example.com/' ) ), 109 109 110 // Schemeless URL containing colons cause parse errors in PHP 7+. 111 array( '//fonts.googleapis.com/css?family=Open+Sans:400&subset=latin', array( 112 'host' => 'fonts.googleapis.com', 113 'path' => '/css', 114 'query' => 'family=Open+Sans:400&subset=latin', 115 ) ), 116 array( '//fonts.googleapis.com/css?family=Open+Sans:400', array( 117 'host' => 'fonts.googleapis.com', 118 'path' => '/css', 119 'query' => 'family=Open+Sans:400', 120 ) ), 121 122 // Query parameter starting with & instead of ? 123 array( 'http://www.test.com/path1/path2/&q=a', array( 124 'scheme' => 'http', 125 'host' => 'www.test.com', 126 'path' => '/path1/path2/', 127 'query' => 'q=a', 128 ) ), 129 array( 'http://www.test.com/path1/path2/file.php&q=a', array( 130 'scheme' => 'http', 131 'host' => 'www.test.com', 132 'path' => '/path1/path2/file.php', 133 'query' => 'q=a', 134 ) ), 135 136 // Empty string or non-string passed in. 137 array( '', array( 138 'path' => '', 139 ) ), 140 array( 123, array( 141 'path' => '123', 142 ) ), 110 143 ); 111 144 /* 112 145 Untestable edge cases in various PHP: … … class Tests_HTTP_HTTP extends WP_UnitTestCase { 117 150 118 151 /** 119 152 * @ticket 36356 120 153 */ 121 154 function test_wp_parse_url_with_default_component() { 122 155 $actual = wp_parse_url( self::FULL_TEST_URL, -1 ); 123 156 $this->assertEquals( array( … … class Tests_HTTP_HTTP extends WP_UnitTestCase { 175 208 // PHP's parse_url() calls this an invalid URL, we handle it as a path. 176 209 array( '/://example.com/', PHP_URL_PATH, '/://example.com/' ), 177 210 211 // Schemeless URL containing colons cause parse errors in PHP 7+. 212 array( '//fonts.googleapis.com/css?family=Open+Sans:400&subset=latin', PHP_URL_HOST, 'fonts.googleapis.com' ), 213 array( '//fonts.googleapis.com/css?family=Open+Sans:400&subset=latin', PHP_URL_PORT, null ), 214 array( '//fonts.googleapis.com/css?family=Open+Sans:400&subset=latin', PHP_URL_PATH, '/css' ), 215 array( '//fonts.googleapis.com/css?family=Open+Sans:400&subset=latin', PHP_URL_QUERY, 'family=Open+Sans:400&subset=latin' ), 216 array( '//fonts.googleapis.com/css?family=Open+Sans:400', PHP_URL_HOST, 'fonts.googleapis.com' ), // 25 217 array( '//fonts.googleapis.com/css?family=Open+Sans:400', PHP_URL_PORT, null ), 218 array( '//fonts.googleapis.com/css?family=Open+Sans:400', PHP_URL_PATH, '/css' ), //27 219 array( '//fonts.googleapis.com/css?family=Open+Sans:400', PHP_URL_QUERY, 'family=Open+Sans:400' ), //28 220 221 // Query parameter starting with & instead of ? 222 array( 'http://www.test.com/path1/path2/&q=a', PHP_URL_PATH, '/path1/path2/' ), 223 array( 'http://www.test.com/path1/path2/&q=a', PHP_URL_QUERY, 'q=a' ), 224 array( 'http://www.test.com/path1/path2/file.php&q=a', PHP_URL_PATH, '/path1/path2/file.php' ), 225 array( 'http://www.test.com/path1/path2/file.php&q=a', PHP_URL_QUERY, 'q=a' ), 226 227 // Empty string or non-string passed in. 228 array( '', PHP_URL_PATH, '' ), 229 array( '', PHP_URL_QUERY, null ), 230 array( 123, PHP_URL_PORT, null ), 231 array( 123, PHP_URL_PATH, '123' ), 178 232 ); 179 233 } 180 234 … … class Tests_HTTP_HTTP extends WP_UnitTestCase { 224 278 } 225 279 } 226 280 } 281 282 /** 283 * @ticket 36356 284 * 285 * @dataProvider get_component_from_parsed_url_array_testcases 286 */ 287 function test_get_component_from_parsed_url_array( $url, $component, $expected ) { 288 $parts = wp_parse_url( $url ); 289 $actual = _get_component_from_parsed_url_array( $parts, $component ); 290 $this->assertSame( $expected, $actual ); 291 } 292 293 function get_component_from_parsed_url_array_testcases() { 294 // 0: A URL, 1: PHP URL constant, 2: The expected result. 295 return array( 296 array( 'http://example.com/', -1, array( 'scheme' => 'http', 'host' => 'example.com', 'path' => '/' ) ), 297 array( 'http://example.com/', -1, array( 'scheme' => 'http', 'host' => 'example.com', 'path' => '/' ) ), 298 array( 'http://example.com/', PHP_URL_HOST, 'example.com' ), 299 array( 'http://example.com/', PHP_URL_USER, null ), 300 array( 'http:///example.com', -1, false ), // Malformed. 301 array( 'http:///example.com', PHP_URL_HOST, null ), // Malformed. 302 ); 303 } 304 305 /** 306 * @ticket 36356 307 * 308 * @dataProvider wp_translate_php_url_constant_to_key_testcases 309 */ 310 function test_wp_translate_php_url_constant_to_key( $input, $expected ) { 311 $actual = _wp_translate_php_url_constant_to_key( $input ); 312 $this->assertSame( $expected, $actual ); 313 } 314 315 function wp_translate_php_url_constant_to_key_testcases() { 316 // 0: PHP URL constant, 1: The expected result. 317 return array( 318 array( PHP_URL_SCHEME, 'scheme' ), 319 array( PHP_URL_HOST, 'host' ), 320 array( PHP_URL_PORT, 'port' ), 321 array( PHP_URL_USER, 'user' ), 322 array( PHP_URL_PASS, 'pass' ), 323 array( PHP_URL_PATH, 'path' ), 324 array( PHP_URL_QUERY, 'query' ), 325 array( PHP_URL_FRAGMENT, 'fragment' ), 326 327 // Test with non-PHP_URL_CONSTANT parameter. 328 array( 'something', false ), 329 array( ABSPATH, false ), 330 ); 331 } 332 227 333 }