Make WordPress Core


Ignore:
File:
1 edited

Legend:

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

    r18254 r17282  
    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 *
    3034 * @package WordPress
    3135 * @subpackage HTTP
     
    3337 */
    3438class 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    }
    35169
    36170    /**
     
    77211     * @param string $url URI resource.
    78212     * @param str|array $args Optional. Override the defaults.
    79      * @return array|object Array containing 'headers', 'body', 'response', 'cookies', 'filename'. A WP_Error instance upon error
     213     * @return array containing 'headers', 'body', 'response', 'cookies'
    80214     */
    81215    function request( $url, $args = array() ) {
     
    94228            'compress' => false,
    95229            'decompress' => true,
    96             'sslverify' => true,
    97             'stream' => false,
    98             'filename' => null
     230            'sslverify' => true
    99231        );
    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;
    108232
    109233        $r = wp_parse_args( $args, $defaults );
    110234        $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'];
    114235
    115236        // Allow plugins to short-circuit the request
     
    134255        $r['local'] = $homeURL['host'] == $arrURL['host'] || 'localhost' == $arrURL['host'];
    135256        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         }
    148257
    149258        if ( is_null( $r['headers'] ) )
     
    178287            if ( ($r['method'] == 'POST' || $r['method'] == 'PUT') && ! isset( $r['headers']['Content-Length'] ) )
    179288                $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 );
    180294        } else {
    181295            if ( is_array( $r['body'] ) || is_object( $r['body'] ) ) {
    182                 $r['body'] = http_build_query( $r['body'], null, '&' );
     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, '&' );
    183300                $r['headers']['Content-Type'] = 'application/x-www-form-urlencoded; charset=' . get_option( 'blog_charset' );
    184301                $r['headers']['Content-Length'] = strlen( $r['body'] );
     
    187304            if ( ! isset( $r['headers']['Content-Length'] ) && ! isset( $r['headers']['content-length'] ) )
    188305                $r['headers']['Content-Length'] = strlen( $r['body'] );
    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 );
     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;
    262327    }
    263328
     
    272337     * @param string $url URI resource.
    273338     * @param str|array $args Optional. Override the defaults.
    274      * @return array|object Array containing 'headers', 'body', 'response', 'cookies', 'filename'. A WP_Error instance upon error
     339     * @return boolean
    275340     */
    276341    function post($url, $args = array()) {
     
    290355     * @param string $url URI resource.
    291356     * @param str|array $args Optional. Override the defaults.
    292      * @return array|object Array containing 'headers', 'body', 'response', 'cookies', 'filename'. A WP_Error instance upon error
     357     * @return boolean
    293358     */
    294359    function get($url, $args = array()) {
     
    308373     * @param string $url URI resource.
    309374     * @param str|array $args Optional. Override the defaults.
    310      * @return array|object Array containing 'headers', 'body', 'response', 'cookies', 'filename'. A WP_Error instance upon error
     375     * @return boolean
    311376     */
    312377    function head($url, $args = array()) {
     
    329394        $res = explode("\r\n\r\n", $strResponse, 2);
    330395
    331         return array('headers' => $res[0], 'body' => isset($res[1]) ? $res[1] : '');
     396        return array('headers' => isset($res[0]) ? $res[0] : array(), 'body' => isset($res[1]) ? $res[1] : '');
    332397    }
    333398
     
    370435        $cookies = array();
    371436        $newheaders = array();
    372         foreach ( (array) $headers as $tempheader ) {
     437        foreach ( $headers as $tempheader ) {
    373438            if ( empty($tempheader) )
    374439                continue;
    375440
    376441            if ( false === strpos($tempheader, ':') ) {
    377                 $stack = explode(' ', $tempheader, 3);
    378                 $stack[] = '';
    379                 list( , $response['code'], $response['message']) = $stack;
     442                list( , $response['code'], $response['message']) = explode(' ', $tempheader, 3);
    380443                continue;
    381444            }
     
    562625     * @param string $url URI resource.
    563626     * @param str|array $args Optional. Override the defaults.
    564      * @return array 'headers', 'body', 'response', 'cookies' and 'filename' keys.
     627     * @return array 'headers', 'body', 'cookies' and 'response' keys.
    565628     */
    566629    function request($url, $args = array()) {
     
    686749
    687750        $strResponse = '';
    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 );
     751        while ( ! feof($handle) )
     752            $strResponse .= fread($handle, 4096);
     753
     754        fclose($handle);
    726755
    727756        if ( true === $secure_transport )
    728757            error_reporting($error_reporting);
    729758
    730         $arrHeaders = WP_Http::processHeaders( $process['headers'] );
     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']);
    731765
    732766        // If location is found, then assume redirect and redirect to location.
    733         if ( isset($arrHeaders['headers']['location']) && 0 !== $r['_redirection'] ) {
     767        if ( 'HEAD' != $r['method'] && isset($arrHeaders['headers']['location']) ) {
    734768            if ( $r['redirection']-- > 0 ) {
    735769                return $this->request($arrHeaders['headers']['location'], $r);
     
    746780            $process['body'] = WP_Http_Encoding::decompress( $process['body'] );
    747781
    748         return array( 'headers' => $arrHeaders['headers'], 'body' => $process['body'], 'response' => $arrHeaders['response'], 'cookies' => $arrHeaders['cookies'], 'filename' => $r['filename'] );
     782        return array('headers' => $arrHeaders['headers'], 'body' => $process['body'], 'response' => $arrHeaders['response'], 'cookies' => $arrHeaders['cookies']);
    749783    }
    750784
     
    757791     */
    758792    function test( $args = array() ) {
    759         if ( ! function_exists( 'fsockopen' ) )
    760             return false;
    761 
    762793        if ( false !== ($option = get_option( 'disable_fsockopen' )) && time()-$option < 43200 ) // 12 hours
    763794            return false;
    764795
    765         $is_ssl = isset( $args['ssl'] ) && $args['ssl'];
    766 
    767         if ( $is_ssl && ! extension_loaded( 'openssl' ) )
     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 */
     820class 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')) )
    768933            return false;
    769934
    770         return apply_filters( 'use_fsockopen_transport', true, $args );
     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);
    771955    }
    772956}
     
    793977     * @param string $url
    794978     * @param str|array $args Optional. Override the defaults.
    795      * @return array 'headers', 'body', 'response', 'cookies' and 'filename' keys.
     979     * @return array 'headers', 'body', 'cookies' and 'response' keys.
    796980     */
    797981    function request($url, $args = array()) {
     
    8661050        }
    8671051
     1052        if ( 'HEAD' == $r['method'] ) // Disable redirects for HEAD requests
     1053            $arrContext['http']['max_redirects'] = 1;
     1054
    8681055        if ( ! empty($r['body'] ) )
    8691056            $arrContext['http']['content'] = $r['body'];
     
    8891076        }
    8901077
    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 );
     1078        $strResponse = stream_get_contents($handle);
     1079        $meta = stream_get_meta_data($handle);
     1080
     1081        fclose($handle);
    9111082
    9121083        $processedHeaders = array();
     
    9161087            $processedHeaders = WP_Http::processHeaders($meta['wrapper_data']);
    9171088
    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 
    9231089        if ( ! empty( $strResponse ) && isset( $processedHeaders['headers']['transfer-encoding'] ) && 'chunked' == $processedHeaders['headers']['transfer-encoding'] )
    9241090            $strResponse = WP_Http::chunkTransferDecode($strResponse);
     
    9271093            $strResponse = WP_Http_Encoding::decompress( $strResponse );
    9281094
    929         return array( 'headers' => $processedHeaders['headers'], 'body' => $strResponse, 'response' => $processedHeaders['response'], 'cookies' => $processedHeaders['cookies'], 'filename' => $r['filename'] );
     1095        return array('headers' => $processedHeaders['headers'], 'body' => $strResponse, 'response' => $processedHeaders['response'], 'cookies' => $processedHeaders['cookies']);
    9301096    }
    9311097
     
    9391105     * @return boolean False means this class can not be used, true means it can.
    9401106     */
    941     function test( $args = array() ) {
    942         if ( ! function_exists( 'fopen' ) )
     1107    function test($args = array()) {
     1108        if ( ! function_exists('fopen') || (function_exists('ini_get') && true != ini_get('allow_url_fopen')) )
    9431109            return false;
    9441110
    945         if ( ! function_exists( 'ini_get' ) || true != ini_get( 'allow_url_fopen' ) )
     1111        if ( version_compare(PHP_VERSION, '5.0', '<') )
    9461112            return false;
    9471113
    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 );
     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);
    9541127    }
    9551128}
    9561129
    9571130/**
    958  * HTTP request method uses Curl extension to retrieve the url.
    959  *
    960  * Requires the Curl extension to be installed.
     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.
    9611136 *
    9621137 * @package WordPress
    9631138 * @subpackage HTTP
    964  * @since 2.7
     1139 * @since 2.7.0
    9651140 */
    966 class 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.
     1141class WP_Http_ExtHttp {
     1142    /**
     1143     * Send a HTTP request to a URI using HTTP extension.
     1144     *
     1145     * Does not support non-blocking.
    9791146     *
    9801147     * @access public
    981      * @since 2.7.0
     1148     * @since 2.7
    9821149     *
    9831150     * @param string $url
    9841151     * @param str|array $args Optional. Override the defaults.
    985      * @return array 'headers', 'body', 'response', 'cookies' and 'filename' keys.
     1152     * @return array 'headers', 'body', 'cookies' and 'response' keys.
    9861153     */
    9871154    function request($url, $args = array()) {
     
    10031170        }
    10041171
    1005         // Construct Cookie: header if any cookies are set.
     1172        // Construct Cookie: header if any cookies are set
    10061173        WP_Http::buildCookieHeader( $r );
    10071174
    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         }
     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);
    10241194
    10251195        $is_local = isset($args['local']) && $args['local'];
     
    10301200            $ssl_verify = apply_filters('https_ssl_verify', $ssl_verify);
    10311201
     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 */
     1292class 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
    10321357
    10331358        // CURLOPT_TIMEOUT and CURLOPT_CONNECTTIMEOUT expect integers.  Have to use ceil since
     
    10391364        curl_setopt( $handle, CURLOPT_URL, $url);
    10401365        curl_setopt( $handle, CURLOPT_RETURNTRANSFER, true );
    1041         curl_setopt( $handle, CURLOPT_SSL_VERIFYHOST, ( $ssl_verify === true ) ? 2 : false );
     1366        curl_setopt( $handle, CURLOPT_SSL_VERIFYHOST, $ssl_verify );
    10421367        curl_setopt( $handle, CURLOPT_SSL_VERIFYPEER, $ssl_verify );
    10431368        curl_setopt( $handle, CURLOPT_USERAGENT, $r['user-agent'] );
     
    10591384
    10601385        if ( true === $r['blocking'] )
    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         }
     1386            curl_setopt( $handle, CURLOPT_HEADER, true );
     1387        else
     1388            curl_setopt( $handle, CURLOPT_HEADER, false );
    10751389
    10761390        // The option doesn't work with safe mode or when open_basedir is set.
    1077         if ( !ini_get('safe_mode') && !ini_get('open_basedir') && 0 !== $r['_redirection'] )
     1391        // Disable HEAD when making HEAD requests.
     1392        if ( !ini_get('safe_mode') && !ini_get('open_basedir') && 'HEAD' != $r['method'] )
    10781393            curl_setopt( $handle, CURLOPT_FOLLOWLOCATION, true );
    10791394
     
    11041419
    11051420        $theResponse = curl_exec( $handle );
    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)) ) {
     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 {
    11141435            if ( $curl_error = curl_error($handle) )
    11151436                return new WP_Error('http_request_failed', $curl_error);
    11161437            if ( in_array( curl_getinfo( $handle, CURLINFO_HTTP_CODE ), array(301, 302) ) )
    11171438                return new WP_Error('http_request_failed', __('Too many redirects.'));
    1118         }
    1119 
    1120         unset( $this->headers );
     1439
     1440            $theHeaders = array( 'headers' => array(), 'cookies' => array() );
     1441            $theBody = '';
     1442        }
    11211443
    11221444        $response = array();
     
    11261448        curl_close( $handle );
    11271449
    1128         if ( $r['stream'] )
    1129             fclose( $stream_handle );
    1130 
    11311450        // See #11305 - When running under safe mode, redirection is disabled above. Handle it manually.
    1132         if ( ! empty( $theHeaders['headers']['location'] ) && ( ini_get( 'safe_mode' ) || ini_get( 'open_basedir' ) ) && 0 !== $r['_redirection'] ) {
     1451        if ( !empty($theHeaders['headers']['location']) && (ini_get('safe_mode') || ini_get('open_basedir')) ) {
    11331452            if ( $r['redirection']-- > 0 ) {
    1134                 return $this->request( $theHeaders['headers']['location'], $r );
     1453                return $this->request($theHeaders['headers']['location'], $r);
    11351454            } else {
    1136                 return new WP_Error( 'http_request_failed', __( 'Too many redirects.' ) );
     1455                return new WP_Error('http_request_failed', __('Too many redirects.'));
    11371456            }
    11381457        }
     
    11411460            $theBody = WP_Http_Encoding::decompress( $theBody );
    11421461
    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 );
     1462        return array('headers' => $theHeaders['headers'], 'body' => $theBody, 'response' => $response, 'cookies' => $theHeaders['cookies']);
    11581463    }
    11591464
     
    11661471     * @return boolean False means this class can not be used, true means it can.
    11671472     */
    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 );
     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;
    11811478    }
    11821479}
     
    11901487 *
    11911488 * Please note that only BASIC authentication is supported by most transports.
    1192  * cURL MAY support more methods (such as NTLM authentication) depending on your environment.
     1489 * cURL and the PHP HTTP Extension MAY support more methods (such as NTLM authentication) depending on your environment.
    11931490 *
    11941491 * The constants are as follows:
     
    14231720     */
    14241721    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    }
    14251733
    14261734    /**
Note: See TracChangeset for help on using the changeset viewer.