Make WordPress Core


Ignore:
File:
1 edited

Legend:

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

    r17282 r18254  
    2828 * Debugging includes several actions, which pass different variables for debugging the HTTP API.
    2929 *
    30  * <strong>http_transport_get_debug</strong> - gives working, nonblocking, and blocking transports.
    31  *
    32  * <strong>http_transport_post_debug</strong> - gives working, nonblocking, and blocking transports.
    33  *
    3430 * @package WordPress
    3531 * @subpackage HTTP
     
    3733 */
    3834class WP_Http {
    39 
    40     /**
    41      * PHP4 style Constructor - Calls PHP5 Style Constructor
    42      *
    43      * @since 2.7.0
    44      * @return WP_Http
    45      */
    46     function WP_Http() {
    47         $this->__construct();
    48     }
    49 
    50     /**
    51      * PHP5 style Constructor - Set up available transport if not available.
    52      *
    53      * PHP4 does not have the 'self' keyword and since WordPress supports PHP4, the class needs to
    54      * be used for the static call. The transport are set up to save time and will only be created
    55      * once. This class can be created many times without having to go through the step of finding
    56      * which transports are available.
    57      *
    58      * @since 2.7.0
    59      * @return WP_Http
    60      */
    61     function __construct() {
    62         WP_Http::_getTransport();
    63         WP_Http::_postTransport();
    64     }
    65 
    66     /**
    67      * Tests the WordPress HTTP objects for an object to use and returns it.
    68      *
    69      * Tests all of the objects and returns the object that passes. Also caches that object to be
    70      * used later.
    71      *
    72      * The order for the GET/HEAD requests are HTTP Extension, cURL, Streams, Fopen, and finally
    73      * Fsockopen. fsockopen() is used last, because it has the most overhead in its implementation.
    74      * There isn't any real way around it, since redirects have to be supported, much the same way
    75      * the other transports also handle redirects.
    76      *
    77      * There are currently issues with "localhost" not resolving correctly with DNS. This may cause
    78      * an error "failed to open stream: A connection attempt failed because the connected party did
    79      * not properly respond after a period of time, or established connection failed because [the]
    80      * connected host has failed to respond."
    81      *
    82      * @since 2.7.0
    83      * @access private
    84      *
    85      * @param array $args Request args, default us an empty array
    86      * @return object|null Null if no transports are available, HTTP transport object.
    87      */
    88     function &_getTransport( $args = array() ) {
    89         static $working_transport, $blocking_transport, $nonblocking_transport;
    90 
    91         if ( is_null($working_transport) ) {
    92             if ( true === WP_Http_ExtHttp::test($args) ) {
    93                 $working_transport['exthttp'] = new WP_Http_ExtHttp();
    94                 $blocking_transport[] = &$working_transport['exthttp'];
    95             } else if ( true === WP_Http_Curl::test($args) ) {
    96                 $working_transport['curl'] = new WP_Http_Curl();
    97                 $blocking_transport[] = &$working_transport['curl'];
    98             } else if ( true === WP_Http_Streams::test($args) ) {
    99                 $working_transport['streams'] = new WP_Http_Streams();
    100                 $blocking_transport[] = &$working_transport['streams'];
    101             } else if ( true === WP_Http_Fopen::test($args) ) {
    102                 $working_transport['fopen'] = new WP_Http_Fopen();
    103                 $blocking_transport[] = &$working_transport['fopen'];
    104             } else if ( true === WP_Http_Fsockopen::test($args) ) {
    105                 $working_transport['fsockopen'] = new WP_Http_Fsockopen();
    106                 $blocking_transport[] = &$working_transport['fsockopen'];
    107             }
    108 
    109             foreach ( array('curl', 'streams', 'fopen', 'fsockopen', 'exthttp') as $transport ) {
    110                 if ( isset($working_transport[$transport]) )
    111                     $nonblocking_transport[] = &$working_transport[$transport];
    112             }
    113         }
    114 
    115         do_action( 'http_transport_get_debug', $working_transport, $blocking_transport, $nonblocking_transport );
    116 
    117         if ( isset($args['blocking']) && !$args['blocking'] )
    118             return $nonblocking_transport;
    119         else
    120             return $blocking_transport;
    121     }
    122 
    123     /**
    124      * Tests the WordPress HTTP objects for an object to use and returns it.
    125      *
    126      * Tests all of the objects and returns the object that passes. Also caches
    127      * that object to be used later. This is for posting content to a URL and
    128      * is used when there is a body. The plain Fopen Transport can not be used
    129      * to send content, but the streams transport can. This is a limitation that
    130      * is addressed here, by just not including that transport.
    131      *
    132      * @since 2.7.0
    133      * @access private
    134      *
    135      * @param array $args Request args, default us an empty array
    136      * @return object|null Null if no transports are available, HTTP transport object.
    137      */
    138     function &_postTransport( $args = array() ) {
    139         static $working_transport, $blocking_transport, $nonblocking_transport;
    140 
    141         if ( is_null($working_transport) ) {
    142             if ( true === WP_Http_ExtHttp::test($args) ) {
    143                 $working_transport['exthttp'] = new WP_Http_ExtHttp();
    144                 $blocking_transport[] = &$working_transport['exthttp'];
    145             } else if ( true === WP_Http_Curl::test($args) ) {
    146                 $working_transport['curl'] = new WP_Http_Curl();
    147                 $blocking_transport[] = &$working_transport['curl'];
    148             } else if ( true === WP_Http_Streams::test($args) ) {
    149                 $working_transport['streams'] = new WP_Http_Streams();
    150                 $blocking_transport[] = &$working_transport['streams'];
    151             } else if ( true === WP_Http_Fsockopen::test($args) ) {
    152                 $working_transport['fsockopen'] = new WP_Http_Fsockopen();
    153                 $blocking_transport[] = &$working_transport['fsockopen'];
    154             }
    155 
    156             foreach ( array('curl', 'streams', 'fsockopen', 'exthttp') as $transport ) {
    157                 if ( isset($working_transport[$transport]) )
    158                     $nonblocking_transport[] = &$working_transport[$transport];
    159             }
    160         }
    161 
    162         do_action( 'http_transport_post_debug', $working_transport, $blocking_transport, $nonblocking_transport );
    163 
    164         if ( isset($args['blocking']) && !$args['blocking'] )
    165             return $nonblocking_transport;
    166         else
    167             return $blocking_transport;
    168     }
    16935
    17036    /**
     
    21177     * @param string $url URI resource.
    21278     * @param str|array $args Optional. Override the defaults.
    213      * @return array containing 'headers', 'body', 'response', 'cookies'
     79     * @return array|object Array containing 'headers', 'body', 'response', 'cookies', 'filename'. A WP_Error instance upon error
    21480     */
    21581    function request( $url, $args = array() ) {
     
    22894            'compress' => false,
    22995            'decompress' => true,
    230             'sslverify' => true
     96            'sslverify' => true,
     97            'stream' => false,
     98            'filename' => null
    23199        );
     100
     101
     102        // Pre-parse for the HEAD checks.
     103        $args = wp_parse_args( $args );
     104
     105        // By default, Head requests do not cause redirections.
     106        if ( isset($args['method']) && 'HEAD' == $args['method'] )
     107            $defaults['redirection'] = 0;
    232108
    233109        $r = wp_parse_args( $args, $defaults );
    234110        $r = apply_filters( 'http_request_args', $r, $url );
     111
     112        // Certain classes decrement this, store a copy of the original value for loop purposes.
     113        $r['_redirection'] = $r['redirection'];
    235114
    236115        // Allow plugins to short-circuit the request
     
    255134        $r['local'] = $homeURL['host'] == $arrURL['host'] || 'localhost' == $arrURL['host'];
    256135        unset( $homeURL );
     136
     137        // If we are streaming to a file but no filename was given drop it in the WP temp dir
     138        // and pick it's name using the basename of the $url
     139        if ( $r['stream']  && empty( $r['filename'] ) )
     140            $r['filename'] = get_temp_dir() . basename( $url );
     141
     142        // Force some settings if we are streaming to a file and check for existence and perms of destination directory
     143        if ( $r['stream'] ) {
     144            $r['blocking'] = true;
     145            if ( ! is_writable( dirname( $r['filename'] ) ) )
     146                return new WP_Error( 'http_request_failed', __( 'Destination directory for file streaming does not exist or is not writable.' ) );
     147        }
    257148
    258149        if ( is_null( $r['headers'] ) )
     
    287178            if ( ($r['method'] == 'POST' || $r['method'] == 'PUT') && ! isset( $r['headers']['Content-Length'] ) )
    288179                $r['headers']['Content-Length'] = 0;
    289 
    290             // The method is ambiguous, because we aren't talking about HTTP methods, the "get" in
    291             // this case is simply that we aren't sending any bodies and to get the transports that
    292             // don't support sending bodies along with those which do.
    293             $transports = WP_Http::_getTransport( $r );
    294180        } else {
    295181            if ( is_array( $r['body'] ) || is_object( $r['body'] ) ) {
    296                 if ( ! version_compare(phpversion(), '5.1.2', '>=') )
    297                     $r['body'] = _http_build_query( $r['body'], null, '&' );
    298                 else
    299                     $r['body'] = http_build_query( $r['body'], null, '&' );
     182                $r['body'] = http_build_query( $r['body'], null, '&' );
    300183                $r['headers']['Content-Type'] = 'application/x-www-form-urlencoded; charset=' . get_option( 'blog_charset' );
    301184                $r['headers']['Content-Length'] = strlen( $r['body'] );
     
    304187            if ( ! isset( $r['headers']['Content-Length'] ) && ! isset( $r['headers']['content-length'] ) )
    305188                $r['headers']['Content-Length'] = strlen( $r['body'] );
    306 
    307             // The method is ambiguous, because we aren't talking about HTTP methods, the "post" in
    308             // this case is simply that we are sending HTTP body and to get the transports that do
    309             // support sending the body. Not all do, depending on the limitations of the PHP core
    310             // limitations.
    311             $transports = WP_Http::_postTransport( $r );
    312         }
    313 
    314         do_action( 'http_api_debug', $transports, 'transports_list' );
    315 
    316         $response = array( 'headers' => array(), 'body' => '', 'response' => array('code' => false, 'message' => false), 'cookies' => array() );
    317         foreach ( (array) $transports as $transport ) {
    318             $response = $transport->request( $url, $r );
    319 
    320             do_action( 'http_api_debug', $response, 'response', get_class( $transport ) );
    321 
    322             if ( ! is_wp_error( $response ) )
    323                 return apply_filters( 'http_response', $response, $r, $url );
    324         }
    325 
    326         return $response;
     189        }
     190
     191        return $this->_dispatch_request($url, $r);
     192    }
     193
     194    /**
     195     * Tests which transports are capable of supporting the request.
     196     *
     197     * @since 3.2.0
     198     * @access private
     199     *
     200     * @param array $args Request arguments
     201     * @param string $url URL to Request
     202     *
     203     * @return string|false Class name for the first transport that claims to support the request.  False if no transport claims to support the request.
     204     */
     205    public function _get_first_available_transport( $args, $url = null ) {
     206        $request_order = array( 'curl', 'streams', 'fsockopen' );
     207
     208        // Loop over each transport on each HTTP request looking for one which will serve this request's needs
     209        foreach ( $request_order as $transport ) {
     210            $class = 'WP_HTTP_' . $transport;
     211
     212            // Check to see if this transport is a possibility, calls the transport statically
     213            if ( !call_user_func( array( $class, 'test' ), $args, $url ) )
     214                continue;
     215
     216            return $class;
     217        }
     218
     219        return false;
     220    }
     221
     222    /**
     223     * Dispatches a HTTP request to a supporting transport.
     224     *
     225     * Tests each transport in order to find a transport which matches the request arguements.
     226     * Also caches the transport instance to be used later.
     227     *
     228     * The order for blocking requests is cURL, Streams, and finally Fsockopen.
     229     * The order for non-blocking requests is cURL, Streams and Fsockopen().
     230     *
     231     * There are currently issues with "localhost" not resolving correctly with DNS. This may cause
     232     * an error "failed to open stream: A connection attempt failed because the connected party did
     233     * not properly respond after a period of time, or established connection failed because [the]
     234     * connected host has failed to respond."
     235     *
     236     * @since 3.2.0
     237     * @access private
     238     *
     239     * @param string $url URL to Request
     240     * @param array $args Request arguments
     241     * @return array|object Array containing 'headers', 'body', 'response', 'cookies', 'filename'. A WP_Error instance upon error
     242     */
     243    private function _dispatch_request( $url, $args ) {
     244        static $transports = array();
     245
     246        $class = $this->_get_first_available_transport( $args, $url );
     247        if ( !$class )
     248            return new WP_Error( 'http_failure', __( 'There are no HTTP transports available which can complete the requested request.' ) );
     249
     250        // Transport claims to support request, instantiate it and give it a whirl.
     251        if ( empty( $transports[$class] ) )
     252            $transports[$class] = new $class;
     253
     254        $response = $transports[$class]->request( $url, $args );
     255
     256        do_action( 'http_api_debug', $response, 'response', $class );
     257
     258        if ( is_wp_error( $response ) )
     259            return $response;
     260
     261        return apply_filters( 'http_response', $response, $args, $url );
    327262    }
    328263
     
    337272     * @param string $url URI resource.
    338273     * @param str|array $args Optional. Override the defaults.
    339      * @return boolean
     274     * @return array|object Array containing 'headers', 'body', 'response', 'cookies', 'filename'. A WP_Error instance upon error
    340275     */
    341276    function post($url, $args = array()) {
     
    355290     * @param string $url URI resource.
    356291     * @param str|array $args Optional. Override the defaults.
    357      * @return boolean
     292     * @return array|object Array containing 'headers', 'body', 'response', 'cookies', 'filename'. A WP_Error instance upon error
    358293     */
    359294    function get($url, $args = array()) {
     
    373308     * @param string $url URI resource.
    374309     * @param str|array $args Optional. Override the defaults.
    375      * @return boolean
     310     * @return array|object Array containing 'headers', 'body', 'response', 'cookies', 'filename'. A WP_Error instance upon error
    376311     */
    377312    function head($url, $args = array()) {
     
    394329        $res = explode("\r\n\r\n", $strResponse, 2);
    395330
    396         return array('headers' => isset($res[0]) ? $res[0] : array(), 'body' => isset($res[1]) ? $res[1] : '');
     331        return array('headers' => $res[0], 'body' => isset($res[1]) ? $res[1] : '');
    397332    }
    398333
     
    435370        $cookies = array();
    436371        $newheaders = array();
    437         foreach ( $headers as $tempheader ) {
     372        foreach ( (array) $headers as $tempheader ) {
    438373            if ( empty($tempheader) )
    439374                continue;
    440375
    441376            if ( false === strpos($tempheader, ':') ) {
    442                 list( , $response['code'], $response['message']) = explode(' ', $tempheader, 3);
     377                $stack = explode(' ', $tempheader, 3);
     378                $stack[] = '';
     379                list( , $response['code'], $response['message']) = $stack;
    443380                continue;
    444381            }
     
    625562     * @param string $url URI resource.
    626563     * @param str|array $args Optional. Override the defaults.
    627      * @return array 'headers', 'body', 'cookies' and 'response' keys.
     564     * @return array 'headers', 'body', 'response', 'cookies' and 'filename' keys.
    628565     */
    629566    function request($url, $args = array()) {
     
    749686
    750687        $strResponse = '';
    751         while ( ! feof($handle) )
    752             $strResponse .= fread($handle, 4096);
    753 
    754         fclose($handle);
     688        $bodyStarted = false;
     689
     690        // If streaming to a file setup the file handle
     691        if ( $r['stream'] ) {
     692            if ( ! WP_DEBUG )
     693                $stream_handle = @fopen( $r['filename'], 'w+' );
     694            else
     695                $stream_handle = fopen( $r['filename'], 'w+' );
     696            if ( ! $stream_handle )
     697                return new WP_Error( 'http_request_failed', sprintf( __( 'Could not open handle for fopen() to %s' ), $r['filename'] ) );
     698
     699            while ( ! feof($handle) ) {
     700                $block = fread( $handle, 4096 );
     701                if ( $bodyStarted ) {
     702                    fwrite( $stream_handle, $block );
     703                } else {
     704                    $strResponse .= $block;
     705                    if ( strpos( $strResponse, "\r\n\r\n" ) ) {
     706                        $process = WP_Http::processResponse( $strResponse );
     707                        $bodyStarted = true;
     708                        fwrite( $stream_handle, $process['body'] );
     709                        unset( $strResponse );
     710                        $process['body'] = '';
     711                    }
     712                }
     713            }
     714
     715            fclose( $stream_handle );
     716
     717        } else {
     718            while ( ! feof($handle) )
     719                $strResponse .= fread( $handle, 4096 );
     720
     721            $process = WP_Http::processResponse( $strResponse );
     722            unset( $strResponse );
     723        }
     724
     725        fclose( $handle );
    755726
    756727        if ( true === $secure_transport )
    757728            error_reporting($error_reporting);
    758729
    759         $process = WP_Http::processResponse($strResponse);
    760         $arrHeaders = WP_Http::processHeaders($process['headers']);
    761 
    762         // Is the response code within the 400 range?
    763         if ( (int) $arrHeaders['response']['code'] >= 400 && (int) $arrHeaders['response']['code'] < 500 )
    764             return new WP_Error('http_request_failed', $arrHeaders['response']['code'] . ': ' . $arrHeaders['response']['message']);
     730        $arrHeaders = WP_Http::processHeaders( $process['headers'] );
    765731
    766732        // If location is found, then assume redirect and redirect to location.
    767         if ( 'HEAD' != $r['method'] && isset($arrHeaders['headers']['location']) ) {
     733        if ( isset($arrHeaders['headers']['location']) && 0 !== $r['_redirection'] ) {
    768734            if ( $r['redirection']-- > 0 ) {
    769735                return $this->request($arrHeaders['headers']['location'], $r);
     
    780746            $process['body'] = WP_Http_Encoding::decompress( $process['body'] );
    781747
    782         return array('headers' => $arrHeaders['headers'], 'body' => $process['body'], 'response' => $arrHeaders['response'], 'cookies' => $arrHeaders['cookies']);
     748        return array( 'headers' => $arrHeaders['headers'], 'body' => $process['body'], 'response' => $arrHeaders['response'], 'cookies' => $arrHeaders['cookies'], 'filename' => $r['filename'] );
    783749    }
    784750
     
    791757     */
    792758    function test( $args = array() ) {
     759        if ( ! function_exists( 'fsockopen' ) )
     760            return false;
     761
    793762        if ( false !== ($option = get_option( 'disable_fsockopen' )) && time()-$option < 43200 ) // 12 hours
    794763            return false;
    795764
    796         $is_ssl = isset($args['ssl']) && $args['ssl'];
    797 
    798         if ( ! $is_ssl && function_exists( 'fsockopen' ) )
    799             $use = true;
    800         elseif ( $is_ssl && extension_loaded('openssl') && function_exists( 'fsockopen' ) )
    801             $use = true;
    802         else
    803             $use = false;
    804 
    805         return apply_filters('use_fsockopen_transport', $use, $args);
    806     }
    807 }
    808 
    809 /**
    810  * HTTP request method uses fopen function to retrieve the url.
    811  *
    812  * Requires PHP version greater than 4.3.0 for stream support. Does not allow for $context support,
    813  * but should still be okay, to write the headers, before getting the response. Also requires that
    814  * 'allow_url_fopen' to be enabled.
    815  *
    816  * @package WordPress
    817  * @subpackage HTTP
    818  * @since 2.7.0
    819  */
    820 class WP_Http_Fopen {
    821     /**
    822      * Send a HTTP request to a URI using fopen().
    823      *
    824      * This transport does not support sending of headers and body, therefore should not be used in
    825      * the instances, where there is a body and headers.
    826      *
    827      * Notes: Does not support non-blocking mode. Ignores 'redirection' option.
    828      *
    829      * @see WP_Http::retrieve For default options descriptions.
    830      *
    831      * @access public
    832      * @since 2.7.0
    833      *
    834      * @param string $url URI resource.
    835      * @param str|array $args Optional. Override the defaults.
    836      * @return array 'headers', 'body', 'cookies' and 'response' keys.
    837      */
    838     function request($url, $args = array()) {
    839         $defaults = array(
    840             'method' => 'GET', 'timeout' => 5,
    841             'redirection' => 5, 'httpversion' => '1.0',
    842             'blocking' => true,
    843             'headers' => array(), 'body' => null, 'cookies' => array()
    844         );
    845 
    846         $r = wp_parse_args( $args, $defaults );
    847 
    848         $arrURL = parse_url($url);
    849 
    850         if ( false === $arrURL )
    851             return new WP_Error('http_request_failed', sprintf(__('Malformed URL: %s'), $url));
    852 
    853         if ( 'http' != $arrURL['scheme'] && 'https' != $arrURL['scheme'] )
    854             $url = str_replace($arrURL['scheme'], 'http', $url);
    855 
    856         if ( is_null( $r['headers'] ) )
    857             $r['headers'] = array();
    858 
    859         if ( is_string($r['headers']) ) {
    860             $processedHeaders = WP_Http::processHeaders($r['headers']);
    861             $r['headers'] = $processedHeaders['headers'];
    862         }
    863 
    864         $initial_user_agent = ini_get('user_agent');
    865 
    866         if ( !empty($r['headers']) && is_array($r['headers']) ) {
    867             $user_agent_extra_headers = '';
    868             foreach ( $r['headers'] as $header => $value )
    869                 $user_agent_extra_headers .= "\r\n$header: $value";
    870             @ini_set('user_agent', $r['user-agent'] . $user_agent_extra_headers);
    871         } else {
    872             @ini_set('user_agent', $r['user-agent']);
    873         }
    874 
    875         if ( !WP_DEBUG )
    876             $handle = @fopen($url, 'r');
    877         else
    878             $handle = fopen($url, 'r');
    879 
    880         if (! $handle)
    881             return new WP_Error('http_request_failed', sprintf(__('Could not open handle for fopen() to %s'), $url));
    882 
    883         $timeout = (int) floor( $r['timeout'] );
    884         $utimeout = $timeout == $r['timeout'] ? 0 : 1000000 * $r['timeout'] % 1000000;
    885         stream_set_timeout( $handle, $timeout, $utimeout );
    886 
    887         if ( ! $r['blocking'] ) {
    888             fclose($handle);
    889             @ini_set('user_agent', $initial_user_agent); //Clean up any extra headers added
    890             return array( 'headers' => array(), 'body' => '', 'response' => array('code' => false, 'message' => false), 'cookies' => array() );
    891         }
    892 
    893         $strResponse = '';
    894         while ( ! feof($handle) )
    895             $strResponse .= fread($handle, 4096);
    896 
    897         if ( function_exists('stream_get_meta_data') ) {
    898             $meta = stream_get_meta_data($handle);
    899 
    900             $theHeaders = $meta['wrapper_data'];
    901             if ( isset( $meta['wrapper_data']['headers'] ) )
    902                 $theHeaders = $meta['wrapper_data']['headers'];
    903         } else {
    904             //$http_response_header is a PHP reserved variable which is set in the current-scope when using the HTTP Wrapper
    905             //see http://php.oregonstate.edu/manual/en/reserved.variables.httpresponseheader.php
    906             $theHeaders = $http_response_header;
    907         }
    908 
    909         fclose($handle);
    910 
    911         @ini_set('user_agent', $initial_user_agent); //Clean up any extra headers added
    912 
    913         $processedHeaders = WP_Http::processHeaders($theHeaders);
    914 
    915         if ( ! empty( $strResponse ) && isset( $processedHeaders['headers']['transfer-encoding'] ) && 'chunked' == $processedHeaders['headers']['transfer-encoding'] )
    916             $strResponse = WP_Http::chunkTransferDecode($strResponse);
    917 
    918         if ( true === $r['decompress'] && true === WP_Http_Encoding::should_decode($processedHeaders['headers']) )
    919             $strResponse = WP_Http_Encoding::decompress( $strResponse );
    920 
    921         return array('headers' => $processedHeaders['headers'], 'body' => $strResponse, 'response' => $processedHeaders['response'], 'cookies' => $processedHeaders['cookies']);
    922     }
    923 
    924     /**
    925      * Whether this class can be used for retrieving an URL.
    926      *
    927      * @since 2.7.0
    928      * @static
    929      * @return boolean False means this class can not be used, true means it can.
    930      */
    931     function test($args = array()) {
    932         if ( ! function_exists('fopen') || (function_exists('ini_get') && true != ini_get('allow_url_fopen')) )
     765        $is_ssl = isset( $args['ssl'] ) && $args['ssl'];
     766
     767        if ( $is_ssl && ! extension_loaded( 'openssl' ) )
    933768            return false;
    934769
    935         if ( isset($args['method']) && 'HEAD' == $args['method'] ) //This transport cannot make a HEAD request
    936             return false;
    937 
    938         $use = true;
    939         //PHP does not verify SSL certs, We can only make a request via this transports if SSL Verification is turned off.
    940         $is_ssl = isset($args['ssl']) && $args['ssl'];
    941         if ( $is_ssl ) {
    942             $is_local = isset($args['local']) && $args['local'];
    943             $ssl_verify = isset($args['sslverify']) && $args['sslverify'];
    944             if ( $is_local && true != apply_filters('https_local_ssl_verify', true) )
    945                 $use = true;
    946             elseif ( !$is_local && true != apply_filters('https_ssl_verify', true) )
    947                 $use = true;
    948             elseif ( !$ssl_verify )
    949                 $use = true;
    950             else
    951                 $use = false;
    952         }
    953 
    954         return apply_filters('use_fopen_transport', $use, $args);
     770        return apply_filters( 'use_fsockopen_transport', true, $args );
    955771    }
    956772}
     
    977793     * @param string $url
    978794     * @param str|array $args Optional. Override the defaults.
    979      * @return array 'headers', 'body', 'cookies' and 'response' keys.
     795     * @return array 'headers', 'body', 'response', 'cookies' and 'filename' keys.
    980796     */
    981797    function request($url, $args = array()) {
     
    1050866        }
    1051867
    1052         if ( 'HEAD' == $r['method'] ) // Disable redirects for HEAD requests
    1053             $arrContext['http']['max_redirects'] = 1;
    1054 
    1055868        if ( ! empty($r['body'] ) )
    1056869            $arrContext['http']['content'] = $r['body'];
     
    1076889        }
    1077890
    1078         $strResponse = stream_get_contents($handle);
    1079         $meta = stream_get_meta_data($handle);
    1080 
    1081         fclose($handle);
     891        if ( $r['stream'] ) {
     892            if ( ! WP_DEBUG )
     893                $stream_handle = @fopen( $r['filename'], 'w+' );
     894            else
     895                $stream_handle = fopen( $r['filename'], 'w+' );
     896
     897            if ( ! $stream_handle )
     898                return new WP_Error( 'http_request_failed', sprintf( __( 'Could not open handle for fopen() to %s' ), $r['filename'] ) );
     899
     900            stream_copy_to_stream( $handle, $stream_handle );
     901
     902            fclose( $stream_handle );
     903            $strResponse = '';
     904        } else {
     905            $strResponse = stream_get_contents( $handle );
     906        }
     907
     908        $meta = stream_get_meta_data( $handle );
     909
     910        fclose( $handle );
    1082911
    1083912        $processedHeaders = array();
     
    1087916            $processedHeaders = WP_Http::processHeaders($meta['wrapper_data']);
    1088917
     918        // Streams does not provide an error code which we can use to see why the request stream stoped.
     919        // We can however test to see if a location header is present and return based on that.
     920        if ( isset($processedHeaders['headers']['location']) && 0 !== $args['_redirection'] )
     921            return new WP_Error('http_request_failed', __('Too many redirects.'));
     922
    1089923        if ( ! empty( $strResponse ) && isset( $processedHeaders['headers']['transfer-encoding'] ) && 'chunked' == $processedHeaders['headers']['transfer-encoding'] )
    1090924            $strResponse = WP_Http::chunkTransferDecode($strResponse);
     
    1093927            $strResponse = WP_Http_Encoding::decompress( $strResponse );
    1094928
    1095         return array('headers' => $processedHeaders['headers'], 'body' => $strResponse, 'response' => $processedHeaders['response'], 'cookies' => $processedHeaders['cookies']);
     929        return array( 'headers' => $processedHeaders['headers'], 'body' => $strResponse, 'response' => $processedHeaders['response'], 'cookies' => $processedHeaders['cookies'], 'filename' => $r['filename'] );
    1096930    }
    1097931
     
    1105939     * @return boolean False means this class can not be used, true means it can.
    1106940     */
    1107     function test($args = array()) {
    1108         if ( ! function_exists('fopen') || (function_exists('ini_get') && true != ini_get('allow_url_fopen')) )
     941    function test( $args = array() ) {
     942        if ( ! function_exists( 'fopen' ) )
    1109943            return false;
    1110944
    1111         if ( version_compare(PHP_VERSION, '5.0', '<') )
     945        if ( ! function_exists( 'ini_get' ) || true != ini_get( 'allow_url_fopen' ) )
    1112946            return false;
    1113947
    1114         //HTTPS via Proxy was added in 5.1.0
    1115         $is_ssl = isset($args['ssl']) && $args['ssl'];
    1116         if ( $is_ssl && version_compare(PHP_VERSION, '5.1.0', '<') ) {
    1117             $proxy = new WP_HTTP_Proxy();
    1118             /**
    1119              * No URL check, as its not currently passed to the ::test() function
    1120              * In the case where a Proxy is in use, Just bypass this transport for HTTPS.
    1121              */
    1122             if ( $proxy->is_enabled() )
    1123                 return false;
    1124         }
    1125 
    1126         return apply_filters('use_streams_transport', true, $args);
     948        $is_ssl = isset( $args['ssl'] ) && $args['ssl'];
     949
     950        if ( $is_ssl && ! extension_loaded( 'openssl' ) )
     951            return false;
     952
     953        return apply_filters( 'use_streams_transport', true, $args );
    1127954    }
    1128955}
    1129956
    1130957/**
    1131  * HTTP request method uses HTTP extension to retrieve the url.
    1132  *
    1133  * Requires the HTTP extension to be installed. This would be the preferred transport since it can
    1134  * handle a lot of the problems that forces the others to use the HTTP version 1.0. Even if PHP 5.2+
    1135  * is being used, it doesn't mean that the HTTP extension will be enabled.
     958 * HTTP request method uses Curl extension to retrieve the url.
     959 *
     960 * Requires the Curl extension to be installed.
    1136961 *
    1137962 * @package WordPress
    1138963 * @subpackage HTTP
    1139  * @since 2.7.0
     964 * @since 2.7
    1140965 */
    1141 class WP_Http_ExtHttp {
    1142     /**
    1143      * Send a HTTP request to a URI using HTTP extension.
    1144      *
    1145      * Does not support non-blocking.
     966class WP_Http_Curl {
     967
     968    /**
     969     * Temporary header storage for use with streaming to a file.
     970     *
     971     * @since 3.2.0
     972     * @access private
     973     * @var string
     974     */
     975    private $headers = '';
     976
     977    /**
     978     * Send a HTTP request to a URI using cURL extension.
    1146979     *
    1147980     * @access public
    1148      * @since 2.7
     981     * @since 2.7.0
    1149982     *
    1150983     * @param string $url
    1151984     * @param str|array $args Optional. Override the defaults.
    1152      * @return array 'headers', 'body', 'cookies' and 'response' keys.
     985     * @return array 'headers', 'body', 'response', 'cookies' and 'filename' keys.
    1153986     */
    1154987    function request($url, $args = array()) {
     
    11701003        }
    11711004
    1172         // Construct Cookie: header if any cookies are set
     1005        // Construct Cookie: header if any cookies are set.
    11731006        WP_Http::buildCookieHeader( $r );
    11741007
    1175         switch ( $r['method'] ) {
    1176             case 'POST':
    1177                 $r['method'] = HTTP_METH_POST;
    1178                 break;
    1179             case 'HEAD':
    1180                 $r['method'] = HTTP_METH_HEAD;
    1181                 break;
    1182             case 'PUT':
    1183                 $r['method'] =  HTTP_METH_PUT;
    1184                 break;
    1185             case 'GET':
    1186             default:
    1187                 $r['method'] = HTTP_METH_GET;
    1188         }
    1189 
    1190         $arrURL = parse_url($url);
    1191 
    1192         if ( 'http' != $arrURL['scheme'] && 'https' != $arrURL['scheme'] )
    1193             $url = preg_replace('|^' . preg_quote($arrURL['scheme'], '|') . '|', 'http', $url);
     1008        $handle = curl_init();
     1009
     1010        // cURL offers really easy proxy support.
     1011        $proxy = new WP_HTTP_Proxy();
     1012
     1013        if ( $proxy->is_enabled() && $proxy->send_through_proxy( $url ) ) {
     1014
     1015            curl_setopt( $handle, CURLOPT_PROXYTYPE, CURLPROXY_HTTP );
     1016            curl_setopt( $handle, CURLOPT_PROXY, $proxy->host() );
     1017            curl_setopt( $handle, CURLOPT_PROXYPORT, $proxy->port() );
     1018
     1019            if ( $proxy->use_authentication() ) {
     1020                curl_setopt( $handle, CURLOPT_PROXYAUTH, CURLAUTH_ANY );
     1021                curl_setopt( $handle, CURLOPT_PROXYUSERPWD, $proxy->authentication() );
     1022            }
     1023        }
    11941024
    11951025        $is_local = isset($args['local']) && $args['local'];
     
    12001030            $ssl_verify = apply_filters('https_ssl_verify', $ssl_verify);
    12011031
    1202         $r['timeout'] = (int) ceil( $r['timeout'] );
    1203 
    1204         $options = array(
    1205             'timeout' => $r['timeout'],
    1206             'connecttimeout' => $r['timeout'],
    1207             'redirect' => $r['redirection'],
    1208             'useragent' => $r['user-agent'],
    1209             'headers' => $r['headers'],
    1210             'ssl' => array(
    1211                 'verifypeer' => $ssl_verify,
    1212                 'verifyhost' => $ssl_verify
    1213             )
    1214         );
    1215 
    1216         if ( HTTP_METH_HEAD == $r['method'] )
    1217             $options['redirect'] = 0; // Assumption: Docs seem to suggest that this means do not follow. Untested.
    1218 
    1219         // The HTTP extensions offers really easy proxy support.
    1220         $proxy = new WP_HTTP_Proxy();
    1221 
    1222         if ( $proxy->is_enabled() && $proxy->send_through_proxy( $url ) ) {
    1223             $options['proxyhost'] = $proxy->host();
    1224             $options['proxyport'] = $proxy->port();
    1225             $options['proxytype'] = HTTP_PROXY_HTTP;
    1226 
    1227             if ( $proxy->use_authentication() ) {
    1228                 $options['proxyauth'] = $proxy->authentication();
    1229                 $options['proxyauthtype'] = HTTP_AUTH_ANY;
    1230             }
    1231         }
    1232 
    1233         if ( !WP_DEBUG ) //Emits warning level notices for max redirects and timeouts
    1234             $strResponse = @http_request($r['method'], $url, $r['body'], $options, $info);
    1235         else
    1236             $strResponse = http_request($r['method'], $url, $r['body'], $options, $info); //Emits warning level notices for max redirects and timeouts
    1237 
    1238         // Error may still be set, Response may return headers or partial document, and error
    1239         // contains a reason the request was aborted, eg, timeout expired or max-redirects reached.
    1240         if ( false === $strResponse || ! empty($info['error']) )
    1241             return new WP_Error('http_request_failed', $info['response_code'] . ': ' . $info['error']);
    1242 
    1243         if ( ! $r['blocking'] )
    1244             return array( 'headers' => array(), 'body' => '', 'response' => array('code' => false, 'message' => false), 'cookies' => array() );
    1245 
    1246         $headers_body = WP_HTTP::processResponse($strResponse);
    1247         $theHeaders = $headers_body['headers'];
    1248         $theBody = $headers_body['body'];
    1249         unset($headers_body);
    1250 
    1251         $theHeaders = WP_Http::processHeaders($theHeaders);
    1252 
    1253         if ( ! empty( $theBody ) && isset( $theHeaders['headers']['transfer-encoding'] ) && 'chunked' == $theHeaders['headers']['transfer-encoding'] ) {
    1254             if ( !WP_DEBUG )
    1255                 $theBody = @http_chunked_decode($theBody);
    1256             else
    1257                 $theBody = http_chunked_decode($theBody);
    1258         }
    1259 
    1260         if ( true === $r['decompress'] && true === WP_Http_Encoding::should_decode($theHeaders['headers']) )
    1261             $theBody = http_inflate( $theBody );
    1262 
    1263         $theResponse = array();
    1264         $theResponse['code'] = $info['response_code'];
    1265         $theResponse['message'] = get_status_header_desc($info['response_code']);
    1266 
    1267         return array('headers' => $theHeaders['headers'], 'body' => $theBody, 'response' => $theResponse, 'cookies' => $theHeaders['cookies']);
    1268     }
    1269 
    1270     /**
    1271      * Whether this class can be used for retrieving an URL.
    1272      *
    1273      * @static
    1274      * @since 2.7.0
    1275      *
    1276      * @return boolean False means this class can not be used, true means it can.
    1277      */
    1278     function test($args = array()) {
    1279         return apply_filters('use_http_extension_transport', function_exists('http_request'), $args );
    1280     }
    1281 }
    1282 
    1283 /**
    1284  * HTTP request method uses Curl extension to retrieve the url.
    1285  *
    1286  * Requires the Curl extension to be installed.
    1287  *
    1288  * @package WordPress
    1289  * @subpackage HTTP
    1290  * @since 2.7
    1291  */
    1292 class WP_Http_Curl {
    1293 
    1294     /**
    1295      * Send a HTTP request to a URI using cURL extension.
    1296      *
    1297      * @access public
    1298      * @since 2.7.0
    1299      *
    1300      * @param string $url
    1301      * @param str|array $args Optional. Override the defaults.
    1302      * @return array 'headers', 'body', 'cookies' and 'response' keys.
    1303      */
    1304     function request($url, $args = array()) {
    1305         $defaults = array(
    1306             'method' => 'GET', 'timeout' => 5,
    1307             'redirection' => 5, 'httpversion' => '1.0',
    1308             'blocking' => true,
    1309             'headers' => array(), 'body' => null, 'cookies' => array()
    1310         );
    1311 
    1312         $r = wp_parse_args( $args, $defaults );
    1313 
    1314         if ( isset($r['headers']['User-Agent']) ) {
    1315             $r['user-agent'] = $r['headers']['User-Agent'];
    1316             unset($r['headers']['User-Agent']);
    1317         } else if ( isset($r['headers']['user-agent']) ) {
    1318             $r['user-agent'] = $r['headers']['user-agent'];
    1319             unset($r['headers']['user-agent']);
    1320         }
    1321 
    1322         // Construct Cookie: header if any cookies are set.
    1323         WP_Http::buildCookieHeader( $r );
    1324 
    1325         $handle = curl_init();
    1326 
    1327         // cURL offers really easy proxy support.
    1328         $proxy = new WP_HTTP_Proxy();
    1329 
    1330         if ( $proxy->is_enabled() && $proxy->send_through_proxy( $url ) ) {
    1331 
    1332             $isPHP5 = version_compare(PHP_VERSION, '5.0.0', '>=');
    1333 
    1334             if ( $isPHP5 ) {
    1335                 curl_setopt( $handle, CURLOPT_PROXYTYPE, CURLPROXY_HTTP );
    1336                 curl_setopt( $handle, CURLOPT_PROXY, $proxy->host() );
    1337                 curl_setopt( $handle, CURLOPT_PROXYPORT, $proxy->port() );
    1338             } else {
    1339                 curl_setopt( $handle, CURLOPT_PROXY, $proxy->host() .':'. $proxy->port() );
    1340             }
    1341 
    1342             if ( $proxy->use_authentication() ) {
    1343                 if ( $isPHP5 )
    1344                     curl_setopt( $handle, CURLOPT_PROXYAUTH, CURLAUTH_ANY );
    1345 
    1346                 curl_setopt( $handle, CURLOPT_PROXYUSERPWD, $proxy->authentication() );
    1347             }
    1348         }
    1349 
    1350         $is_local = isset($args['local']) && $args['local'];
    1351         $ssl_verify = isset($args['sslverify']) && $args['sslverify'];
    1352         if ( $is_local )
    1353             $ssl_verify = apply_filters('https_local_ssl_verify', $ssl_verify);
    1354         elseif ( ! $is_local )
    1355             $ssl_verify = apply_filters('https_ssl_verify', $ssl_verify);
    1356 
    13571032
    13581033        // CURLOPT_TIMEOUT and CURLOPT_CONNECTTIMEOUT expect integers.  Have to use ceil since
     
    13641039        curl_setopt( $handle, CURLOPT_URL, $url);
    13651040        curl_setopt( $handle, CURLOPT_RETURNTRANSFER, true );
    1366         curl_setopt( $handle, CURLOPT_SSL_VERIFYHOST, $ssl_verify );
     1041        curl_setopt( $handle, CURLOPT_SSL_VERIFYHOST, ( $ssl_verify === true ) ? 2 : false );
    13671042        curl_setopt( $handle, CURLOPT_SSL_VERIFYPEER, $ssl_verify );
    13681043        curl_setopt( $handle, CURLOPT_USERAGENT, $r['user-agent'] );
     
    13841059
    13851060        if ( true === $r['blocking'] )
    1386             curl_setopt( $handle, CURLOPT_HEADER, true );
    1387         else
    1388             curl_setopt( $handle, CURLOPT_HEADER, false );
     1061            curl_setopt( $handle, CURLOPT_HEADERFUNCTION, array( &$this, 'stream_headers' ) );
     1062
     1063        curl_setopt( $handle, CURLOPT_HEADER, false );
     1064
     1065        // If streaming to a file open a file handle, and setup our curl streaming handler
     1066        if ( $r['stream'] ) {
     1067            if ( ! WP_DEBUG )
     1068                $stream_handle = @fopen( $r['filename'], 'w+' );
     1069            else
     1070                $stream_handle = fopen( $r['filename'], 'w+' );
     1071            if ( ! $stream_handle )
     1072                return new WP_Error( 'http_request_failed', sprintf( __( 'Could not open handle for fopen() to %s' ), $r['filename'] ) );
     1073            curl_setopt( $handle, CURLOPT_FILE, $stream_handle );
     1074        }
    13891075
    13901076        // The option doesn't work with safe mode or when open_basedir is set.
    1391         // Disable HEAD when making HEAD requests.
    1392         if ( !ini_get('safe_mode') && !ini_get('open_basedir') && 'HEAD' != $r['method'] )
     1077        if ( !ini_get('safe_mode') && !ini_get('open_basedir') && 0 !== $r['_redirection'] )
    13931078            curl_setopt( $handle, CURLOPT_FOLLOWLOCATION, true );
    13941079
     
    14191104
    14201105        $theResponse = curl_exec( $handle );
    1421 
    1422         if ( !empty($theResponse) ) {
    1423             $headerLength = curl_getinfo($handle, CURLINFO_HEADER_SIZE);
    1424             $theHeaders = trim( substr($theResponse, 0, $headerLength) );
    1425             if ( strlen($theResponse) > $headerLength )
    1426                 $theBody = substr( $theResponse, $headerLength );
    1427             else
    1428                 $theBody = '';
    1429             if ( false !== strpos($theHeaders, "\r\n\r\n") ) {
    1430                 $headerParts = explode("\r\n\r\n", $theHeaders);
    1431                 $theHeaders = $headerParts[ count($headerParts) -1 ];
    1432             }
    1433             $theHeaders = WP_Http::processHeaders($theHeaders);
    1434         } else {
     1106        $theBody = '';
     1107        $theHeaders = WP_Http::processHeaders( $this->headers );
     1108
     1109        if ( strlen($theResponse) > 0 && ! is_bool( $theResponse ) ) // is_bool: when using $args['stream'], curl_exec will return (bool)true
     1110            $theBody = $theResponse;
     1111
     1112        // If no response, and It's not a HEAD request with valid headers returned
     1113        if ( 0 == strlen($theResponse) && ('HEAD' != $args['method'] || empty($this->headers)) ) {
    14351114            if ( $curl_error = curl_error($handle) )
    14361115                return new WP_Error('http_request_failed', $curl_error);
    14371116            if ( in_array( curl_getinfo( $handle, CURLINFO_HTTP_CODE ), array(301, 302) ) )
    14381117                return new WP_Error('http_request_failed', __('Too many redirects.'));
    1439 
    1440             $theHeaders = array( 'headers' => array(), 'cookies' => array() );
    1441             $theBody = '';
    1442         }
     1118        }
     1119
     1120        unset( $this->headers );
    14431121
    14441122        $response = array();
     
    14481126        curl_close( $handle );
    14491127
     1128        if ( $r['stream'] )
     1129            fclose( $stream_handle );
     1130
    14501131        // See #11305 - When running under safe mode, redirection is disabled above. Handle it manually.
    1451         if ( !empty($theHeaders['headers']['location']) && (ini_get('safe_mode') || ini_get('open_basedir')) ) {
     1132        if ( ! empty( $theHeaders['headers']['location'] ) && ( ini_get( 'safe_mode' ) || ini_get( 'open_basedir' ) ) && 0 !== $r['_redirection'] ) {
    14521133            if ( $r['redirection']-- > 0 ) {
    1453                 return $this->request($theHeaders['headers']['location'], $r);
     1134                return $this->request( $theHeaders['headers']['location'], $r );
    14541135            } else {
    1455                 return new WP_Error('http_request_failed', __('Too many redirects.'));
     1136                return new WP_Error( 'http_request_failed', __( 'Too many redirects.' ) );
    14561137            }
    14571138        }
     
    14601141            $theBody = WP_Http_Encoding::decompress( $theBody );
    14611142
    1462         return array('headers' => $theHeaders['headers'], 'body' => $theBody, 'response' => $response, 'cookies' => $theHeaders['cookies']);
     1143        return array( 'headers' => $theHeaders['headers'], 'body' => $theBody, 'response' => $response, 'cookies' => $theHeaders['cookies'], 'filename' => $r['filename'] );
     1144    }
     1145
     1146    /**
     1147     * Grab the headers of the cURL request
     1148     *
     1149     * Each header is sent individually to this callback, so we append to the $header property for temporary storage
     1150     *
     1151     * @since 3.2.0
     1152     * @access private
     1153     * @return int
     1154     */
     1155    private function stream_headers( $handle, $headers ) {
     1156        $this->headers .= $headers;
     1157        return strlen( $headers );
    14631158    }
    14641159
     
    14711166     * @return boolean False means this class can not be used, true means it can.
    14721167     */
    1473     function test($args = array()) {
    1474         if ( function_exists('curl_init') && function_exists('curl_exec') )
    1475             return apply_filters('use_curl_transport', true, $args);
    1476 
    1477         return false;
     1168    function test( $args = array() ) {
     1169        if ( ! function_exists( 'curl_init' ) || ! function_exists( 'curl_exec' ) )
     1170            return false;
     1171
     1172        $is_ssl = isset( $args['ssl'] ) && $args['ssl'];
     1173
     1174        if ( $is_ssl ) {
     1175            $curl_version = curl_version();
     1176            if ( ! (CURL_VERSION_SSL & $curl_version['features']) ) // Does this cURL version support SSL requests?
     1177                return false;
     1178        }
     1179
     1180        return apply_filters( 'use_curl_transport', true, $args );
    14781181    }
    14791182}
     
    14871190 *
    14881191 * Please note that only BASIC authentication is supported by most transports.
    1489  * cURL and the PHP HTTP Extension MAY support more methods (such as NTLM authentication) depending on your environment.
     1192 * cURL MAY support more methods (such as NTLM authentication) depending on your environment.
    14901193 *
    14911194 * The constants are as follows:
     
    17201423     */
    17211424    var $domain;
    1722 
    1723     /**
    1724      * PHP4 style Constructor - Calls PHP5 Style Constructor.
    1725      *
    1726      * @access public
    1727      * @since 2.8.0
    1728      * @param string|array $data Raw cookie data.
    1729      */
    1730     function WP_Http_Cookie( $data ) {
    1731         $this->__construct( $data );
    1732     }
    17331425
    17341426    /**
Note: See TracChangeset for help on using the changeset viewer.