Make WordPress Core

Ticket #36356: 36356.diff

File 36356.diff, 13.5 KB (added by peterwilsoncc, 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 ) { 
    623623}
    624624
    625625/**
    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.
    627628 *
    628629 * 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.
    631640 *
    632641 * Error suppression is used as prior to PHP 5.3.3, an E_WARNING would be generated
    633642 * when URL parsing failed.
    634643 *
    635644 * @since 4.4.0
    636645 * @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.
    637647 *
    638648 * @param string $url       The URL to parse.
    639649 * @param int    $component The specific component to retrieve. Use one of the PHP
    640650 *                          predefined constants to specify which one.
    641651 *                          Defaults to -1 (= return all parts as an array).
    642652 *                          @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't
    645  *               exist in the given URL; a sting or - in the case of PHP_URL_PORT - integer
    646  *               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.
    647657 */
    648658function wp_parse_url( $url, $component = -1 ) {
    649         $parts = @parse_url( $url, $component );
     659        $parts = @parse_url( $url );
    650660
    651         if ( version_compare( PHP_VERSION, '5.4.7', '>=' ) ) {
    652                 return $parts;
    653         }
     661        $schemeless_with_colon = ( '//' === substr( $url, 0, 2 ) && false !== strpos( $url, ':' ) );
    654662
    655663        if ( false === $parts ) {
     664                $url = strval( $url );
     665
    656666                // < 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                                }
    660684                        }
    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 );
    664689                        }
     690
    665691                        // Remove the placeholder values.
    666                         if ( -1 === $component ) {
    667                                 unset( $parts['scheme'], $parts['host'] );
     692                        foreach ( $to_unset as $key ) {
     693                                unset( $parts[ $key ] );
    668694                        }
    669695                } else {
    670                         return $parts;
     696                        return _get_component_from_parsed_url_array( $parts, $component );
    671697                }
    672698        }
    673699
    674700        // < 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'] );
    698717                }
    699718        }
    700719
    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 */
     747function _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 */
     772function _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        }
    702789}
  • 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 { 
    107107                        // PHP's parse_url() calls this an invalid url, we handle it as a path
    108108                        array( '/://example.com/', array( 'path' => '/://example.com/' ) ),
    109109
     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                        ) ),
    110143                );
    111144                /*
    112145                Untestable edge cases in various PHP:
    class Tests_HTTP_HTTP extends WP_UnitTestCase { 
    117150
    118151        /**
    119152         * @ticket 36356
    120     */
     153        */
    121154        function test_wp_parse_url_with_default_component() {
    122155                $actual = wp_parse_url( self::FULL_TEST_URL, -1 );
    123156                $this->assertEquals( array(
    class Tests_HTTP_HTTP extends WP_UnitTestCase { 
    175208                        // PHP's parse_url() calls this an invalid URL, we handle it as a path.
    176209                        array( '/://example.com/', PHP_URL_PATH, '/://example.com/' ),
    177210
     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' ),
    178232                );
    179233        }
    180234
    class Tests_HTTP_HTTP extends WP_UnitTestCase { 
    224278                        }
    225279                }
    226280        }
     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
    227333}