Make WordPress Core

Changeset 37428


Ignore:
Timestamp:
05/13/2016 04:41:45 AM (10 years ago)
Author:
rmccue
Message:

HTTP API: Replace internals with Requests library.

Requests is a library very similar to WP_HTTP, with a high level of unit test coverage, and has a common lineage and development team. It also supports parallel requests.

See #33055.

Location:
trunk
Files:
69 added
6 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-includes/class-http.php

    r37342 r37428  
    77 * @since 2.7.0
    88 */
     9
     10if ( ! class_exists( 'Requests' ) ) {
     11        require( ABSPATH . WPINC . '/class-requests.php' );
     12
     13        Requests::register_autoloader();
     14        Requests::set_certificate_path( ABSPATH . WPINC . '/certificates/ca-bundle.crt' );
     15}
    916
    1017/**
    … …  
    248255
    249256                if ( function_exists( 'wp_kses_bad_protocol' ) ) {
    250                         if ( $r['reject_unsafe_urls'] )
     257                        if ( $r['reject_unsafe_urls'] ) {
    251258                                $url = wp_http_validate_url( $url );
     259                        }
    252260                        if ( $url ) {
    253261                                $url = wp_kses_bad_protocol( $url, array( 'http', 'https', 'ssl' ) );
    … …  
    257265                $arrURL = @parse_url( $url );
    258266
    259                 if ( empty( $url ) || empty( $arrURL['scheme'] ) )
     267                if ( empty( $url ) || empty( $arrURL['scheme'] ) ) {
    260268                        return new WP_Error('http_request_failed', __('A valid URL was not provided.'));
    261 
    262                 if ( $this->block_request( $url ) )
     269                }
     270
     271                if ( $this->block_request( $url ) ) {
    263272                        return new WP_Error( 'http_request_failed', __( 'User has blocked requests through HTTP.' ) );
    264 
    265                 /*
    266                  * Determine if this is a https call and pass that on to the transport functions
    267                  * so that we can blacklist the transports that do not support ssl verification
     273                }
     274
     275                // If we are streaming to a file but no filename was given drop it in the WP temp dir
     276                // and pick its name using the basename of the $url
     277                if ( $r['stream'] ) {
     278                        if ( empty( $r['filename'] ) ) {
     279                                $r['filename'] = get_temp_dir() . basename( $url );
     280                        }
     281
     282                        // Force some settings if we are streaming to a file and check for existence and perms of destination directory
     283                        $r['blocking'] = true;
     284                        if ( ! wp_is_writable( dirname( $r['filename'] ) ) ) {
     285                                return new WP_Error( 'http_request_failed', __( 'Destination directory for file streaming does not exist or is not writable.' ) );
     286                        }
     287                }
     288
     289                if ( is_null( $r['headers'] ) ) {
     290                        $r['headers'] = array();
     291                }
     292
     293                // WP allows passing in headers as a string, weirdly.
     294                if ( ! is_array( $r['headers'] ) ) {
     295                        $processedHeaders = WP_Http::processHeaders( $r['headers'] );
     296                        $r['headers'] = $processedHeaders['headers'];
     297                }
     298
     299                // Setup arguments
     300                $headers = $r['headers'];
     301                $data = $r['body'];
     302                $type = $r['method'];
     303                $options = array(
     304                        'timeout' => $r['timeout'],
     305                        'useragent' => $r['user-agent'],
     306                        'blocking' => $r['blocking'],
     307                );
     308
     309                if ( $r['stream'] ) {
     310                        $options['filename'] = $r['filename'];
     311                }
     312                if ( empty( $r['redirection'] ) ) {
     313                        $options['follow_redirects'] = false;
     314                }
     315                else {
     316                        $options['redirects'] = $r['redirection'];
     317                }
     318
     319                // Use byte limit, if we can
     320                if ( isset( $r['limit_response_size'] ) ) {
     321                        $options['max_bytes'] = $r['limit_response_size'];
     322                }
     323
     324                // If we've got cookies, use them
     325                if ( ! empty( $r['cookies'] ) ) {
     326                        $options['cookies'] = $r['cookies'];
     327                }
     328
     329                // SSL certificate handling
     330                if ( ! $r['sslverify'] ) {
     331                        $options['verify'] = false;
     332                }
     333                else {
     334                        $options['verify'] = $r['sslcertificates'];
     335                }
     336
     337                /**
     338                 * Filter whether SSL should be verified for non-local requests.
     339                 *
     340                 * @since 2.8.0
     341                 *
     342                 * @param bool $ssl_verify Whether to verify the SSL connection. Default true.
    268343                 */
    269                 $r['ssl'] = $arrURL['scheme'] == 'https' || $arrURL['scheme'] == 'ssl';
    270 
    271                 // Determine if this request is to OUR install of WordPress.
    272                 $homeURL = parse_url( get_bloginfo( 'url' ) );
    273                 $r['local'] = 'localhost' == $arrURL['host'] || ( isset( $homeURL['host'] ) && $homeURL['host'] == $arrURL['host'] );
    274                 unset( $homeURL );
    275 
    276                 /*
    277                  * If we are streaming to a file but no filename was given drop it in the WP temp dir
    278                  * and pick its name using the basename of the $url.
     344                $options['verify'] = apply_filters( 'https_ssl_verify', $options['verify'] );
     345
     346                try {
     347                        $response = Requests::request( $url, $headers, $data, $type, $options );
     348                }
     349                catch ( Requests_Exception $e ) {
     350                        $response = new WP_Error( 'http_request_failed', $e->getMessage() );
     351                }
     352
     353                /**
     354                 * Fires after an HTTP API response is received and before the response is returned.
     355                 *
     356                 * @since 2.8.0
     357                 *
     358                 * @param array|WP_Error $response HTTP response or WP_Error object.
     359                 * @param string         $context  Context under which the hook is fired.
     360                 * @param string         $class    HTTP transport used.
     361                 * @param array          $args     HTTP request arguments.
     362                 * @param string         $url      The request URL.
    279363                 */
    280                 if ( $r['stream']  && empty( $r['filename'] ) ) {
    281                         $r['filename'] = get_temp_dir() . wp_unique_filename( get_temp_dir(), basename( $url ) );
    282                 }
    283 
    284                 /*
    285                  * Force some settings if we are streaming to a file and check for existence and perms
    286                  * of destination directory.
     364                do_action( 'http_api_debug', $response, 'response', 'Requests', $r, $url );
     365                if ( is_wp_error( $response ) ) {
     366                        return $response;
     367                }
     368
     369                if ( ! $r['blocking'] ) {
     370                        return array(
     371                                'headers' => array(),
     372                                'body' => '',
     373                                'response' => array(
     374                                        'code' => false,
     375                                        'message' => false,
     376                                ),
     377                                'cookies' => array(),
     378                        );
     379                }
     380
     381                // Convert the response into an array
     382                $data = new WP_HTTP_Requests_Response( $response, $r['filename'] );
     383
     384                /**
     385                 * Filter the HTTP API response immediately before the response is returned.
     386                 *
     387                 * @since 2.9.0
     388                 *
     389                 * @param array  $data HTTP response.
     390                 * @param array  $r    HTTP request arguments.
     391                 * @param string $url  The request URL.
    287392                 */
    288                 if ( $r['stream'] ) {
    289                         $r['blocking'] = true;
    290                         if ( ! wp_is_writable( dirname( $r['filename'] ) ) )
    291                                 return new WP_Error( 'http_request_failed', __( 'Destination directory for file streaming does not exist or is not writable.' ) );
    292                 }
    293 
    294                 if ( is_null( $r['headers'] ) )
    295                         $r['headers'] = array();
    296 
    297                 if ( ! is_array( $r['headers'] ) ) {
    298                         $processedHeaders = self::processHeaders( $r['headers'], $url );
    299                         $r['headers'] = $processedHeaders['headers'];
    300                 }
    301 
    302                 if ( isset( $r['headers']['User-Agent'] ) ) {
    303                         $r['user-agent'] = $r['headers']['User-Agent'];
    304                         unset( $r['headers']['User-Agent'] );
    305                 }
    306 
    307                 if ( isset( $r['headers']['user-agent'] ) ) {
    308                         $r['user-agent'] = $r['headers']['user-agent'];
    309                         unset( $r['headers']['user-agent'] );
    310                 }
    311 
    312                 if ( '1.1' == $r['httpversion'] && !isset( $r['headers']['connection'] ) ) {
    313                         $r['headers']['connection'] = 'close';
    314                 }
    315 
    316                 // Construct Cookie: header if any cookies are set.
    317                 self::buildCookieHeader( $r );
    318 
    319                 // Avoid issues where mbstring.func_overload is enabled.
    320                 mbstring_binary_safe_encoding();
    321 
    322                 if ( ! isset( $r['headers']['Accept-Encoding'] ) ) {
    323                         if ( $encoding = WP_Http_Encoding::accept_encoding( $url, $r ) )
    324                                 $r['headers']['Accept-Encoding'] = $encoding;
    325                 }
    326 
    327                 if ( ( ! is_null( $r['body'] ) && '' != $r['body'] ) || 'POST' == $r['method'] || 'PUT' == $r['method'] ) {
    328                         if ( is_array( $r['body'] ) || is_object( $r['body'] ) ) {
    329                                 $r['body'] = http_build_query( $r['body'], null, '&' );
    330 
    331                                 if ( ! isset( $r['headers']['Content-Type'] ) )
    332                                         $r['headers']['Content-Type'] = 'application/x-www-form-urlencoded; charset=' . get_option( 'blog_charset' );
    333                         }
    334 
    335                         if ( '' === $r['body'] )
    336                                 $r['body'] = null;
    337 
    338                         if ( ! isset( $r['headers']['Content-Length'] ) && ! isset( $r['headers']['content-length'] ) )
    339                                 $r['headers']['Content-Length'] = strlen( $r['body'] );
    340                 }
    341 
    342                 $response = $this->_dispatch_request( $url, $r );
    343 
    344                 reset_mbstring_encoding();
    345 
    346                 if ( is_wp_error( $response ) )
    347                         return $response;
    348 
    349                 // Append cookies that were used in this request to the response
    350                 if ( ! empty( $r['cookies'] ) ) {
    351                         $cookies_set = wp_list_pluck( $response['cookies'], 'name' );
    352                         foreach ( $r['cookies'] as $cookie ) {
    353                                 if ( ! in_array( $cookie->name, $cookies_set ) && $cookie->test( $url ) ) {
    354                                         $response['cookies'][] = $cookie;
    355                                 }
    356                         }
    357                 }
    358 
    359                 return $response;
     393                return apply_filters( 'http_response', $data, $r, $url );
     394        }
     395
     396        /**
     397         * Match redirect behaviour to browser handling.
     398         *
     399         * Changes 302 redirects from POST to GET to match browser handling. Per
     400         * RFC 7231, user agents can deviate from the strict reading of the
     401         * specification for compatibility purposes.
     402         *
     403         * @param string $location URL to redirect to.
     404         * @param array $headers Headers for the redirect.
     405         * @param array $options Redirect request options.
     406         * @param Requests_Response $original Response object.
     407         */
     408        public static function browser_redirect_compatibility( $location, $headers, $data, &$options, $original ) {
     409                // Browser compat
     410                if ( $original->status_code === 302 ) {
     411                        $options['type'] = Requests::GET;
     412                }
    360413        }
    361414
  • trunk/src/wp-includes/default-filters.php

    r37298 r37428  
    207207add_filter( 'title_save_pre',           'trim'                                );
    208208
    209 add_filter( 'http_request_host_is_external', 'allowed_http_request_hosts', 10, 2 );
     209add_filter( 'http_request_host_is_external',    'allowed_http_request_hosts',                          10, 2 );
     210add_action( 'requests-requests.before_redirect', array( 'WP_Http', 'browser_redirect_compatibility' ), 10, 5 );
    210211
    211212// REST API filters.
  • trunk/src/wp-includes/http.php

    r37115 r37428  
    214214 */
    215215function wp_remote_retrieve_headers( $response ) {
    216         if ( is_wp_error($response) || ! isset($response['headers']) || ! is_array($response['headers']))
     216        if ( is_wp_error( $response ) || ! isset( $response['headers'] ) ) {
    217217                return array();
     218        }
    218219
    219220        return $response['headers'];
    … …  
    230231 */
    231232function wp_remote_retrieve_header( $response, $header ) {
    232         if ( is_wp_error($response) || ! isset($response['headers']) || ! is_array($response['headers']))
     233        if ( is_wp_error( $response ) || ! isset( $response['headers'] ) ) {
    233234                return '';
    234 
    235         if ( array_key_exists($header, $response['headers']) )
     235        }
     236
     237        if ( isset( $response['headers'][ $header ] ) ) {
    236238                return $response['headers'][$header];
     239        }
    237240
    238241        return '';
  • trunk/src/wp-settings.php

    r36566 r37428  
    184184require( ABSPATH . WPINC . '/class-wp-http-encoding.php' );
    185185require( ABSPATH . WPINC . '/class-wp-http-response.php' );
     186require( ABSPATH . WPINC . '/class-wp-http-requests-response.php' );
    186187require( ABSPATH . WPINC . '/widgets.php' );
    187188require( ABSPATH . WPINC . '/class-wp-widget.php' );
  • trunk/tests/phpunit/tests/http/base.php

    r34874 r37428  
    290290                $res = wp_remote_post( add_query_arg( 'response_code', 303, $url ), array( 'timeout' => 30 ) );
    291291                $this->assertEquals( 'GET', wp_remote_retrieve_body( $res ) );
    292 
    293                 // Test 304 - POST to POST
    294                 $res = wp_remote_post( add_query_arg( 'response_code', 304, $url ), array( 'timeout' => 30 ) );
    295                 $this->assertEquals( 'POST', wp_remote_retrieve_body( $res ) );
    296292        }
    297293
  • trunk/tests/phpunit/tests/http/functions.php

    r35734 r37428  
    2020                $headers = wp_remote_retrieve_headers( $response );
    2121
    22                 $this->assertInternalType( 'array', $headers, "Reply wasn't array." );
    2322                $this->assertEquals( 'image/jpeg', $headers['content-type'] );
    2423                $this->assertEquals( '40148', $headers['content-length'] );
    … …  
    3736                $headers = wp_remote_head( $url );
    3837
    39                 $this->assertInternalType( 'array', $headers, "Reply wasn't array." );
    4038                $this->assertEquals( '404', wp_remote_retrieve_response_code( $headers ) );
    4139        }
    … …  
    4846
    4947                // should return the same headers as a head request
    50                 $this->assertInternalType( 'array', $headers, "Reply wasn't array." );
    5148                $this->assertEquals( 'image/jpeg', $headers['content-type'] );
    5249                $this->assertEquals( '40148', $headers['content-length'] );
    … …  
    6259
    6360                // should return the same headers as a head request
    64                 $this->assertInternalType( 'array', $headers, "Reply wasn't array." );
    6561                $this->assertEquals( 'image/jpeg', $headers['content-type'] );
    6662                $this->assertEquals( '40148', $headers['content-length'] );
    … …  
    8682                $cookies  = wp_remote_retrieve_cookies( $response );
    8783
    88                 $this->assertInternalType( 'array', $cookies );
    8984                $this->assertNotEmpty( $cookies );
    9085
Note: See TracChangeset for help on using the changeset viewer.