Make WordPress Core

Ticket #4779: 4779.r8519.diff

File 4779.r8519.diff, 21.5 KB (added by anonymized_218323, 18 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