WordPress.org

Make WordPress Core

Ticket #4779: 4779.r8519.diff

File 4779.r8519.diff, 21.5 KB (added by santosj, 10 years ago)

Adds cURL transport, but does not add it to the list, because it needs to be tested first. Fixes documentation and non-blocking mode.

  • http.php

     
    11<?php
    22/**
    3  * Simple HTTP request fallback system
     3 * Simple and uniform HTTP request API.
    44 *
     5 * Will eventually replace and standardize the WordPress HTTP requests made.
     6 *
    57 * @package WordPress
    68 * @subpackage HTTP
    79 * @since 2.7
     
    911 */
    1012
    1113/**
    12  * Abstract class for all of the fallback implementation
    13  * classes. The implementation classes will extend this class
    14  * to keep common API methods universal between different
    15  * functionality.
     14 * WordPress HTTP Class for managing HTTP Transports and making HTTP requests.
    1615 *
     16 * This class is called for the functionality of making HTTP requests and should
     17 * replace Snoopy functionality, eventually. There is no available functionality
     18 * to add HTTP transport implementations, since most of the HTTP transports are
     19 * added and available for use.
     20 *
     21 * The exception is that cURL is not available as a transport and lacking an
     22 * implementation. It will be added later and should be a patch on the WordPress
     23 * Trac.
     24 *
     25 * There are no properties, because none are needed and for performance reasons.
     26 * Some of the functions are static and while they do have some overhead over
     27 * functions in PHP4, the purpose is maintainability. When PHP5 is finally the
     28 * requirement, it will be easy to add the static keyword to the code. It is not
     29 * as easy to convert a function to a method after enough code uses the old way.
     30 *
    1731 * @package WordPress
    1832 * @subpackage HTTP
    1933 * @since 2.7
     
    3347        /**
    3448         * PHP5 style Constructor - Setup available transport if not available.
    3549         *
     50         * PHP4 does not have the 'self' keyword and since WordPress supports PHP4,
     51         * the class needs to be used for the static call.
     52         *
     53         * The transport are setup to save time. This should only be called once, so
     54         * the overhead should be fine.
     55         *
    3656         * @since 2.7
    3757         * @return WP_Http
    3858         */
    3959        function __construct() {
    4060                WP_Http::_getTransport();
     61                WP_Http::_postTransport();
    4162        }
    4263
    4364        /**
     
    4667         * Tests all of the objects and returns the object that passes. Also caches
    4768         * that object to be used later.
    4869         *
     70         * The order for the GET/HEAD requests are Streams, HTTP Extension, Fopen,
     71         * and finally Fsockopen. fsockopen() is used last, because it has the most
     72         * overhead in its implementation. There isn't any real way around it, since
     73         * redirects have to be supported, much the same way the other transports
     74         * also handle redirects.
     75         *
     76         * There are currently issues with "localhost" not resolving correctly with
     77         * DNS. This may cause an error "failed to open stream: A connection attempt
     78         * failed because the connected party did not properly respond after a
     79         * period of time, or established connection failed because connected host
     80         * has failed to respond."
     81         *
    4982         * @since 2.7
    5083         * @access private
    5184         *
     
    75108         * that object to be used later. This is for posting content to a URL and
    76109         * is used when there is a body. The plain Fopen Transport can not be used
    77110         * to send content, but the streams transport can. This is a limitation that
    78          * is addressed here.
     111         * is addressed here, by just not including that transport.
    79112         *
    80113         * @since 2.7
    81114         * @access private
     
    98131        }
    99132
    100133        /**
    101          * Retrieve the location and set the class properties after the site has been retrieved.
     134         * Send a HTTP request to a URI.
    102135         *
     136         * The only URI that are supported in the HTTP Transport implementation are
     137         * the HTTP and HTTPS protocols. HTTP and HTTPS are assumed so the server
     138         * might not know how to handle the send headers. Other protocols are
     139         * unsupported and most likely will fail.
     140         *
     141         * The defaults are 'method', 'timeout', 'redirection', 'httpversion',
     142         * 'blocking' and 'user-agent'.
     143         *
     144         * Accepted 'method' values are 'GET', 'POST', and 'HEAD', some transports
     145         * technically allow others, but should not be assumed. The 'timeout' is
     146         * used to sent how long the connection should stay open before failing when
     147         * no response. 'redirection' is used to track how many redirects were taken
     148         * and used to sent the amount for other transports, but not all transports
     149         * accept setting that value.
     150         *
     151         * The 'httpversion' option is used to sent the HTTP version and accepted
     152         * values are '1.0', and '1.1' and should be a string. Version 1.1 is not
     153         * supported, because of chunk response. The 'user-agent' option is the
     154         * user-agent and is used to replace the default user-agent, which is
     155         * 'WordPress/WP_Version', where WP_Version is the value from $wp_version.
     156         *
     157         * 'blocking' is the default, which is used to tell the transport, whether
     158         * it should halt PHP while it performs the request or continue regardless.
     159         * Actually, that isn't entirely correct. Blocking mode really just means
     160         * whether the fread should just pull what it can whenever it gets bytes or
     161         * if it should wait until it has enough in the buffer to read or finishes
     162         * reading the entire content. It doesn't actually always mean that PHP will
     163         * continue going after making the request.
     164         *
    103165         * @access public
    104166         * @since 2.7
    105167         *
    106          * @param string $url
     168         * @param string $url URI resource.
     169         * @param str|array $args Optional. Override the defaults.
    107170         * @param string|array $headers Optional. Either the header string or array of Header name and value pairs. Expects sanitized.
    108171         * @param string $body Optional. The body that should be sent. Expected to be already processed.
    109          * @param str|array $type Optional. Should be an already processed array with HTTP arguments.
    110172         * @return boolean
    111173         */
    112174        function request($url, $args = array(), $headers = null, $body = null) {
     
    114176
    115177                $defaults = array(
    116178                        'method' => 'GET', 'timeout' => 3,
    117                         'redirection' => 5, 'redirected' => false,
    118                         'httpversion' => '1.0', 'blocking' => true
     179                        'redirection' => 5, 'httpversion' => '1.0',
     180                        'user-agent' => apply_filters('http_headers_useragent', 'WordPress/' . $wp_version ),
     181                        'blocking' => true
    119182                );
    120183
    121184                $r = wp_parse_args( $args, $defaults );
     
    128191                }
    129192
    130193                if ( ! isset($headers['user-agent']) || ! isset($headers['User-Agent']) )
    131                         $headers['user-agent'] = apply_filters('http_headers_useragent', 'WordPress/' . $wp_version );
     194                        $headers['user-agent'] = $r['user-agent'];
    132195
    133                 if ( is_null($body) )
     196                if ( is_null($body) || count($headers) > 1 )
    134197                        $transport = WP_Http::_getTransport();
    135198                else
    136199                        $transport = WP_Http::_postTransport();
     
    146209         * @access public
    147210         * @since 2.7
    148211         *
    149          * @param string $url The location of the site and page to retrieve.
     212         * @param string $url URI resource.
     213         * @param str|array $args Optional. Override the defaults.
    150214         * @param string|array $headers Optional. Either the header string or array of Header name and value pairs.
    151215         * @param string $body Optional. The body that should be sent. Expected to be already processed.
    152216         * @return boolean
     
    165229         * @access public
    166230         * @since 2.7
    167231         *
     232         * @param string $url URI resource.
     233         * @param str|array $args Optional. Override the defaults.
    168234         * @param string|array $headers Optional. Either the header string or array of Header name and value pairs.
    169235         * @param string $body Optional. The body that should be sent. Expected to be already processed.
    170236         * @return boolean
     
    183249         * @access public
    184250         * @since 2.7
    185251         *
     252         * @param string $url URI resource.
     253         * @param str|array $args Optional. Override the defaults.
    186254         * @param string|array $headers Optional. Either the header string or array of Header name and value pairs.
    187255         * @param string $body Optional. The body that should be sent. Expected to be already processed.
    188256         * @return boolean
     
    227295        /**
    228296         * Whether the headers returned a redirect location.
    229297         *
     298         * Actually just checks whether the location header exists.
     299         *
    230300         * @access public
    231301         * @static
    232302         * @since 2.7
     
    243313        /**
    244314         * Transform header string into an array.
    245315         *
    246          * Will overwrite the last header value, if it is not empty.
     316         * If an array is given, then it will be immediately passed through with no
     317         * changes. This is to prevent overhead in processing headers that don't
     318         * need to be processed. That and it is unknown what kind of effect
     319         * processing the array will have since there is no checking done on whether
     320         * ':' does not exist within the array string.
    247321         *
     322         * Checking could be added, but it is easier and faster to just passed the
     323         * array through and assume that it has already been processed.
     324         *
    248325         * @access public
    249326         * @static
    250327         * @since 2.7
    251328         *
    252329         * @param string|array $headers
    253          * @return array
     330         * @return array Processed string headers
    254331         */
    255332        function processHeaders($headers) {
    256333                if ( is_array($headers) )
    257334                        return $headers;
    258335
    259                 $headers = explode("\n", str_replace(array("\r"), '', $headers) );
     336                $headers = explode("\n", str_replace(array("\r\n", "\r"), "\n", $headers) );
    260337
    261338                $response = array('code' => 0, 'message' => '');
    262339
     
    285362/**
    286363 * HTTP request method uses fsockopen function to retrieve the url.
    287364 *
    288  * Preferred method since it works with all WordPress supported PHP versions.
     365 * This would be the preferred method, but the fsockopen implementation has the
     366 * most overhead of all the HTTP transport implementations.
    289367 *
    290368 * @package WordPress
    291369 * @subpackage HTTP
     
    293371 */
    294372class WP_Http_Fsockopen {
    295373        /**
    296          * Retrieve the location and set the class properties after the site has been retrieved.
     374         * Send a HTTP request to a URI using fsockopen().
    297375         *
     376         * Does not support non-blocking mode.
     377         *
     378         * @see WP_Http::retrieve For default options descriptions.
     379         *
    298380         * @since 2.7
    299381         * @access public
    300          * @param string $url
     382         * @param string $url URI resource.
     383         * @param str|array $args Optional. Override the defaults.
    301384         * @param string|array $headers Optional. Either the header string or array of Header name and value pairs. Expects sanitized.
    302385         * @param string $body Optional. The body that should be sent. Expected to be already processed.
    303          * @param str|array $type Optional. Should be an already processed array with HTTP arguments.
    304          * @return boolean
     386         * @return array 'headers', 'body', and 'response' keys.
    305387         */
    306388        function request($url, $args = array(), $headers = null, $body = null) {
    307389                $defaults = array(
     
    322404                if ( ! isset($arrURL['port']) ) {
    323405                        if ( ($arrURL['scheme'] == 'ssl' || $arrURL['scheme'] == 'https') && extension_loaded('openssl') ) {
    324406                                $arrURL['host'] = 'ssl://' . $arrURL['host'];
    325                                 $arrURL['port'] = apply_filters('http_request_default_port', 443);
     407                                $arrURL['port'] = apply_filters('http_request_port', 443);
    326408                                $secure_transport = true;
    327409                        } else {
    328410                                $arrURL['port'] = apply_filters('http_request_default_port', 80);
     
    331413                        $arrURL['port'] = apply_filters('http_request_port', $arrURL['port']);
    332414                }
    333415
     416                // There are issues with the HTTPS and SSL protocols that cause errors
     417                // that can be safely ignored and should be ignored.
    334418                if ( true === $secure_transport )
    335419                        $error_reporting = error_reporting(0);
    336420
     
    360444
    361445                fwrite($handle, $strHeaders);
    362446
    363                 if ( ! $r['blocking'] )
    364                         return array( 'headers' => array(), 'body' => '', 'response' => array() );
     447                if ( ! $r['blocking'] ) {
     448                        fclose($handle);
     449                        return array( 'headers' => array(), 'body' => '', 'response' => array('code', 'message') );
     450                }
    365451
    366452                $strResponse = '';
    367453                while ( ! feof($handle) )
     
    410496 * for $context support, but should still be okay, to write the headers, before
    411497 * getting the response. Also requires that 'allow_url_fopen' to be enabled.
    412498 *
    413  * Second preferred method for handling the retrieving the url for PHP 4.3+.
    414  *
    415499 * @package WordPress
    416500 * @subpackage HTTP
    417501 * @since 2.7
    418502 */
    419503class WP_Http_Fopen {
    420504        /**
    421          * Retrieve the location and set the class properties after the site has been retrieved.
     505         * Send a HTTP request to a URI using fopen().
    422506         *
     507         * This transport does not support sending of headers and body, therefore
     508         * should not be used in the instances, where there is a body and headers.
     509         *
     510         * Notes: Does not support non-blocking mode. Ignores 'redirection' option.
     511         *
     512         * @see WP_Http::retrieve For default options descriptions.
     513         *
    423514         * @access public
    424515         * @since 2.7
    425516         *
    426          * @param string $url
     517         * @param string $url URI resource.
     518         * @param str|array $args Optional. Override the defaults.
    427519         * @param string|array $headers Optional. Either the header string or array of Header name and value pairs. Expects sanitized.
    428520         * @param string $body Optional. The body that should be sent. Expected to be already processed.
    429          * @param str|array $type Optional. Should be an already processed array with HTTP arguments.
    430          * @return boolean
     521         * @return array 'headers', 'body', and 'response' keys.
    431522         */
    432523        function request($url, $args = array(), $headers = null, $body = null) {
    433524                global $http_response_header;
     
    453544                if ( function_exists('stream_set_timeout') )
    454545                        stream_set_timeout($handle, apply_filters('http_request_timeout', $r['timeout']) );
    455546
    456                 if ( ! $r['blocking'] )
    457                         return array( 'headers' => array(), 'body' => '', 'response' => array() );
     547                if ( ! $r['blocking'] ) {
     548                        fclose($handle);
     549                        return array( 'headers' => array(), 'body' => '', 'response' => array('code', 'message') );
     550                }
    458551
    459552                $strResponse = '';
    460553                while ( ! feof($handle) )
    461554                        $strResponse .= fread($handle, 4096);
    462555
     556                fclose($handle);
     557
    463558                $theHeaders = '';
    464559                if ( function_exists('stream_get_meta_data') ) {
    465560                        $meta = stream_get_meta_data($handle);
     
    467562                } else {
    468563                        $theHeaders = $http_response_header;
    469564                }
    470                 fclose($handle);
     565                $processedHeaders = WP_Http::processHeaders($theHeaders);
    471566
    472                 $processedHeaders = WP_Http::processHeaders($theHeaders);
    473567                return array('headers' => $processedHeaders['headers'], 'body' => $strResponse, 'response' => $processedHeaders['response']);
    474568        }
    475569
     
    501595 */
    502596class WP_Http_Streams {
    503597        /**
    504          * Retrieve the location and set the class properties after the site has been retrieved.
     598         * Send a HTTP request to a URI using streams with fopen().
    505599         *
    506600         * @access public
    507601         * @since 2.7
     
    510604         * @param str|array $args Optional. Override the defaults.
    511605         * @param string|array $headers Optional. Either the header string or array of Header name and value pairs. Expects sanitized.
    512606         * @param string $body Optional. The body that should be sent. Expected to be already processed.
    513          * @return boolean
     607         * @return array 'headers', 'body', and 'response' keys.
    514608         */
    515609        function request($url, $args = array(), $headers = null, $body = null) {
    516610                $defaults = array(
     
    548642                if ( ! $handle)
    549643                        return new WP_Error('http_request_failed', sprintf(__('Could not open handle for fopen() to %s'), $url));
    550644
    551                 if ( ! $r['blocking'] )
    552                         return array( 'headers' => array(), 'body' => '', 'response' => array() );
     645                if ( ! $r['blocking'] ) {
     646                        fclose($handle);
     647                        return array( 'headers' => array(), 'body' => '', 'response' => array('code', 'message') );
     648                }
    553649
    554650                $strResponse = stream_get_contents($handle);
    555651                $meta = stream_get_meta_data($handle);
     652                $processedHeaders = WP_Http::processHeaders($meta['wrapper_data']);
    556653
    557654                fclose($handle);
    558655
    559                 $processedHeaders = WP_Http::processHeaders($meta['wrapper_data']);
    560656                return array('headers' => $processedHeaders['headers'], 'body' => $strResponse, 'response' => $processedHeaders['response']);
    561657        }
    562658
     
    583679/**
    584680 * HTTP request method uses HTTP extension to retrieve the url.
    585681 *
    586  * Requires the HTTP extension to be installed.
     682 * Requires the HTTP extension to be installed. This would be the preferred
     683 * transport since it can handle a lot of the problems that forces the others to
     684 * use the HTTP version 1.0. Even if PHP 5.2+ is being used, it doesn't mean
     685 * that the HTTP extension will be enabled.
    587686 *
    588  * Last ditch effort to retrieve the URL before complete failure.
    589  * Does not support non-blocking requests.
    590  *
    591687 * @package WordPress
    592688 * @subpackage HTTP
    593689 * @since 2.7
    594690 */
    595691class WP_Http_ExtHTTP {
    596692        /**
    597          * Retrieve the location and set the class properties after the site has been retrieved.
     693         * Send a HTTP request to a URI using HTTP extension.
    598694         *
     695         * Does not support non-blocking.
     696         *
    599697         * @access public
    600698         * @since 2.7
    601699         *
     
    603701         * @param str|array $args Optional. Override the defaults.
    604702         * @param string|array $headers Optional. Either the header string or array of Header name and value pairs. Expects sanitized.
    605703         * @param string $body Optional. The body that should be sent. Expected to be already processed.
    606          * @return boolean
     704         * @return array 'headers', 'body', and 'response' keys.
    607705         */
    608706        function request($url, $args = array(), $headers = null, $body = null) {
    609707                global $wp_version;
     
    611709                $defaults = array(
    612710                        'method' => 'GET', 'timeout' => 3,
    613711                        'redirection' => 5, 'httpversion' => '1.0',
    614                         'blocking' => true, 'user_agent' => apply_filters('http_headers_useragent', 'WordPress/' . $wp_version)
     712                        'blocking' => true
    615713                );
    616714
    617715                $r = wp_parse_args( $args, $defaults );
    618716
    619                 if ( isset($headers['User-Agent']) )
     717                if ( isset($headers['User-Agent']) ) {
     718                        $r['user-agent'] = $headers['User-Agent'];
    620719                        unset($headers['User-Agent']);
     720                } else if( isset($headers['user-agent']) ) {
     721                        $r['user-agent'] = $headers['user-agent'];
     722                        unset($headers['user-agent']);
     723                } else {
     724                        $r['user-agent'] = apply_filters('http_headers_useragent', 'WordPress/' . $wp_version );
     725                }
    621726
    622727                switch ( $r['method'] ) {
    623728                        case 'GET':
     
    639744                        $url = str_replace($arrURL['scheme'], 'http', $url);
    640745
    641746                $options = array(
    642                         'timeout' => $this->timeout,
    643                         'connecttimeout' => apply_filters('http_request_stream_timeout', $this->timeout),
    644                         'redirect' => apply_filters('http_request_redirect', 3),
     747                        'timeout' => $r['timeout'],
     748                        'connecttimeout' => $r['timeout'],
     749                        'redirect' => $r['redirection'],
    645750                        'useragent' => $r['user_agent'],
    646751                        'headers' => $headers,
    647752                );
     
    651756                if ( false === $strResponse )
    652757                        return new WP_Error('http_request_failed', $info['response_code'] . ': ' . $info['error']);
    653758
     759                if ( ! $r['blocking'] )
     760                        return array( 'headers' => array(), 'body' => '', 'response' => array('code', 'message') );
     761
    654762                list($theHeaders, $theBody) = explode("\r\n\r\n", $strResponse, 2);
    655763                $theHeaders = WP_Http::processHeaders($theHeaders);
    656764
     
    678786}
    679787
    680788/**
     789 * HTTP request method uses Curl extension to retrieve the url.
     790 *
     791 * Requires the Curl extension to be installed.
     792 *
     793 * @package WordPress
     794 * @subpackage HTTP
     795 * @since 2.7
     796 */
     797class WP_Http_Curl {
     798        /**
     799         * Send a HTTP request to a URI using cURL extension.
     800         *
     801         * @access public
     802         * @since 2.7
     803         *
     804         * @param string $url
     805         * @param str|array $args Optional. Override the defaults.
     806         * @param string|array $headers Optional. Either the header string or array of Header name and value pairs. Expects sanitized.
     807         * @param string $body Optional. The body that should be sent. Expected to be already processed.
     808         * @return array 'headers', 'body', and 'response' keys.
     809         */
     810        function request($url, $args = array(), $headers = null, $body = null) {
     811                global $wp_version;
     812
     813                $defaults = array(
     814                        'method' => 'GET', 'timeout' => 3,
     815                        'redirection' => 5, 'httpversion' => '1.0',
     816                        'blocking' => true
     817                );
     818
     819                $r = wp_parse_args( $args, $defaults );
     820
     821                if ( isset($headers['User-Agent']) ) {
     822                        $r['user-agent'] = $headers['User-Agent'];
     823                        unset($headers['User-Agent']);
     824                } else if( isset($headers['user-agent']) ) {
     825                        $r['user-agent'] = $headers['user-agent'];
     826                        unset($headers['user-agent']);
     827                } else {
     828                        $r['user-agent'] = apply_filters('http_headers_useragent', 'WordPress/' . $wp_version );
     829                }
     830
     831                $handle = curl_init();
     832                curl_setopt( $handle, CURLOPT_URL, $url);
     833
     834                if ( true === $r['blocking'] ) {
     835                        curl_setopt( $handle, CURLOPT_HEADER, true );
     836                } else {
     837                        curl_setopt( $handle, CURLOPT_HEADER, false );
     838                        curl_setopt( $handle, CURLOPT_NOBODY, true );
     839                }
     840
     841                curl_setopt( $handle, CURLOPT_RETURNTRANSFER, 1 );
     842                curl_setopt( $handle, CURLOPT_USERAGENT, $r['user-agent'] );
     843                curl_setopt( $handle, CURLOPT_CONNECTTIMEOUT, 1 );
     844                curl_setopt( $handle, CURLOPT_TIMEOUT, $r['timeout'] );
     845                curl_setopt( $handle, CURLOPT_FOLLOWLOCATION, true );
     846                curl_setopt( $handle, CURLOPT_MAXREDIRS, $r['redirection'] );
     847
     848                if( ! is_null($headers) )
     849                        curl_setopt( $handle, CURLOPT_HTTPHEADER, $headers );
     850
     851                if ( $r['httpversion'] == '1.0' )
     852                        curl_setopt( $headle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0 );
     853                else
     854                        curl_setopt( $headle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1 );
     855
     856                if ( ! $r['blocking'] ) {
     857                        curl_close( $handle );
     858                        return array( 'headers' => array(), 'body' => '', 'response' => array('code', 'message') );
     859                }
     860
     861                $theResponse = curl_exec( $handle );
     862
     863                list($theHeaders, $theBody) = explode("\r\n\r\n", $strResponse, 2);
     864                $theHeaders = WP_Http::processHeaders($theHeaders);
     865
     866                $response = array();
     867                $response['code'] = curl_getinfo( $handle, CURLINFO_HTTP_CODE );
     868                $response['message'] = get_status_header_desc($response['code']);
     869
     870                curl_close( $handle );
     871
     872                return array('headers' => $theHeaders['headers'], 'body' => $theBody, 'response' => $response);
     873        }
     874
     875        /**
     876         * Whether this class can be used for retrieving an URL.
     877         *
     878         * @static
     879         * @since 2.7
     880         *
     881         * @return boolean False means this class can not be used, true means it can.
     882         */
     883        function test() {
     884                if ( function_exists('curl_init') )
     885                        return true;
     886
     887                return false;
     888        }
     889}
     890
     891/**
    681892 * Returns the initialized WP_Http Object
    682893 *
    683894 * @since 2.7