WordPress.org

Make WordPress Core

Changeset 37428


Ignore:
Timestamp:
05/13/16 04:41:45 (15 months 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.