Make WordPress Core

Ticket #33055: 33055.diff

File 33055.diff, 36.1 KB (added by wonderboymusic, 9 years ago)
  • src/wp-includes/class-http.php

     
    8383         *                        A WP_Error instance upon error.
    8484         */
    8585        public function request( $url, $args = array() ) {
    86                 global $wp_version;
     86                if ( is_array( $url ) ) {
     87                        return $this->request_multi( $url );
     88                }
    8789
    88                 $defaults = array(
    89                         'method' => 'GET',
    90                         /**
    91                          * Filter the timeout value for an HTTP request.
    92                          *
    93                          * @since 2.7.0
    94                          *
    95                          * @param int $timeout_value Time in seconds until a request times out.
    96                          *                           Default 5.
    97                          */
    98                         'timeout' => apply_filters( 'http_request_timeout', 5 ),
    99                         /**
    100                          * Filter the number of redirects allowed during an HTTP request.
    101                          *
    102                          * @since 2.7.0
    103                          *
    104                          * @param int $redirect_count Number of redirects allowed. Default 5.
    105                          */
    106                         'redirection' => apply_filters( 'http_request_redirection_count', 5 ),
    107                         /**
    108                          * Filter the version of the HTTP protocol used in a request.
    109                          *
    110                          * @since 2.7.0
    111                          *
    112                          * @param string $version Version of HTTP used. Accepts '1.0' and '1.1'.
    113                          *                        Default '1.0'.
    114                          */
    115                         'httpversion' => apply_filters( 'http_request_version', '1.0' ),
    116                         /**
    117                          * Filter the user agent value sent with an HTTP request.
    118                          *
    119                          * @since 2.7.0
    120                          *
    121                          * @param string $user_agent WordPress user agent string.
    122                          */
    123                         'user-agent' => apply_filters( 'http_headers_useragent', 'WordPress/' . $wp_version . '; ' . get_bloginfo( 'url' ) ),
    124                         /**
    125                          * Filter whether to pass URLs through wp_http_validate_url() in an HTTP request.
    126                          *
    127                          * @since 3.6.0
    128                          *
    129                          * @param bool $pass_url Whether to pass URLs through wp_http_validate_url().
    130                          *                       Default false.
    131                          */
    132                         'reject_unsafe_urls' => apply_filters( 'http_request_reject_unsafe_urls', false ),
    133                         'blocking' => true,
    134                         'headers' => array(),
    135                         'cookies' => array(),
    136                         'body' => null,
    137                         'compress' => false,
    138                         'decompress' => true,
    139                         'sslverify' => true,
    140                         'sslcertificates' => ABSPATH . WPINC . '/certificates/ca-bundle.crt',
    141                         'stream' => false,
    142                         'filename' => null,
    143                         'limit_response_size' => null,
    144                 );
     90                $request = new WP_Http_Request( $url, $args  );
     91                if ( false !== $request->pre ) {
     92                        return $request->pre;
     93                }
    14594
    146                 // Pre-parse for the HEAD checks.
    147                 $args = wp_parse_args( $args );
     95                $response = $this->_dispatch_request( $request->url, $request->args );
    14896
    149                 // By default, Head requests do not cause redirections.
    150                 if ( isset($args['method']) && 'HEAD' == $args['method'] )
    151                         $defaults['redirection'] = 0;
     97                reset_mbstring_encoding();
    15298
    153                 $r = wp_parse_args( $args, $defaults );
    154                 /**
    155                  * Filter the arguments used in an HTTP request.
    156                  *
    157                  * @since 2.7.0
    158                  *
    159                  * @param array  $r   An array of HTTP request arguments.
    160                  * @param string $url The request URL.
    161                  */
    162                 $r = apply_filters( 'http_request_args', $r, $url );
    163 
    164                 // The transports decrement this, store a copy of the original value for loop purposes.
    165                 if ( ! isset( $r['_redirection'] ) )
    166                         $r['_redirection'] = $r['redirection'];
    167 
    168                 /**
    169                  * Filter whether to preempt an HTTP request's return.
    170                  *
    171                  * Returning a truthy value to the filter will short-circuit
    172                  * the HTTP request and return early with that value.
    173                  *
    174                  * @since 2.9.0
    175                  *
    176                  * @param bool   $preempt Whether to preempt an HTTP request return. Default false.
    177                  * @param array  $r       HTTP request arguments.
    178                  * @param string $url     The request URL.
    179                  */
    180                 $pre = apply_filters( 'pre_http_request', false, $r, $url );
    181                 if ( false !== $pre )
    182                         return $pre;
    183 
    184                 if ( function_exists( 'wp_kses_bad_protocol' ) ) {
    185                         if ( $r['reject_unsafe_urls'] )
    186                                 $url = wp_http_validate_url( $url );
    187                         if ( $url ) {
    188                                 $url = wp_kses_bad_protocol( $url, array( 'http', 'https', 'ssl' ) );
    189                         }
     99                if ( is_wp_error( $response ) ) {
     100                        return $response;
    190101                }
    191102
    192                 $arrURL = @parse_url( $url );
     103                $this->add_cookies( $response, $request );
    193104
    194                 if ( empty( $url ) || empty( $arrURL['scheme'] ) )
    195                         return new WP_Error('http_request_failed', __('A valid URL was not provided.'));
     105                return $response;
     106        }
    196107
    197                 if ( $this->block_request( $url ) )
    198                         return new WP_Error( 'http_request_failed', __( 'User has blocked requests through HTTP.' ) );
     108        public static function hash_request( $url, $args ) {
     109                return md5( $url . serialize( $args ) );
     110        }
    199111
    200                 /*
    201                  * Determine if this is a https call and pass that on to the transport functions
    202                  * so that we can blacklist the transports that do not support ssl verification
    203                  */
    204                 $r['ssl'] = $arrURL['scheme'] == 'https' || $arrURL['scheme'] == 'ssl';
    205 
    206                 // Determine if this request is to OUR install of WordPress.
    207                 $homeURL = parse_url( get_bloginfo( 'url' ) );
    208                 $r['local'] = 'localhost' == $arrURL['host'] || ( isset( $homeURL['host'] ) && $homeURL['host'] == $arrURL['host'] );
    209                 unset( $homeURL );
    210 
    211                 /*
    212                  * If we are streaming to a file but no filename was given drop it in the WP temp dir
    213                  * and pick its name using the basename of the $url.
    214                  */
    215                 if ( $r['stream']  && empty( $r['filename'] ) ) {
    216                         $r['filename'] = get_temp_dir() . wp_unique_filename( get_temp_dir(), basename( $url ) );
     112        private function add_cookies( &$response, $request ) {
     113                // Append cookies that were used in this request to the response
     114                if ( empty( $request->args['cookies'] ) ) {
     115                        return;
    217116                }
    218117
    219                 /*
    220                  * Force some settings if we are streaming to a file and check for existence and perms
    221                  * of destination directory.
    222                  */
    223                 if ( $r['stream'] ) {
    224                         $r['blocking'] = true;
    225                         if ( ! wp_is_writable( dirname( $r['filename'] ) ) )
    226                                 return new WP_Error( 'http_request_failed', __( 'Destination directory for file streaming does not exist or is not writable.' ) );
     118                $cookies_set = wp_list_pluck( $response['cookies'], 'name' );
     119                foreach ( $request->args['cookies'] as $cookie ) {
     120                        if ( ! in_array( $cookie->name, $cookies_set ) && $cookie->test( $request->url ) ) {
     121                                $response['cookies'][] = $cookie;
     122                        }
    227123                }
     124        }
    228125
    229                 if ( is_null( $r['headers'] ) )
    230                         $r['headers'] = array();
     126        public function request_multi( $tuples ) {
     127                $requests = array();
     128                $responses = array();
    231129
    232                 if ( ! is_array( $r['headers'] ) ) {
    233                         $processedHeaders = self::processHeaders( $r['headers'], $url );
    234                         $r['headers'] = $processedHeaders['headers'];
     130                foreach ( $tuples as $tuple ) {
     131                        @list( $url, $args, $callback ) = $tuple;
     132                        $request = new WP_Http_Request( $url, $args );
     133                        $hash = self::hash_request( $url, $args );
     134                        if ( false !== $request->pre ) {
     135                                $responses[ $hash ] = $request->pre;
     136                        } else {
     137                                $requests[ $hash ] = $request;
     138                        }
    235139                }
    236140
    237                 if ( isset( $r['headers']['User-Agent'] ) ) {
    238                         $r['user-agent'] = $r['headers']['User-Agent'];
    239                         unset( $r['headers']['User-Agent'] );
     141                if ( ! $requests ) {
     142                        reset_mbstring_encoding();
     143                        return $responses;
    240144                }
    241145
    242                 if ( isset( $r['headers']['user-agent'] ) ) {
    243                         $r['user-agent'] = $r['headers']['user-agent'];
    244                         unset( $r['headers']['user-agent'] );
    245                 }
     146                if ( 1 === count( $requests ) ) {
     147                        $responses[ $hash ] = $this->_dispatch_request( $request->url, $request->args );
     148                        reset_mbstring_encoding();
    246149
    247                 if ( '1.1' == $r['httpversion'] && !isset( $r['headers']['connection'] ) ) {
    248                         $r['headers']['connection'] = 'close';
    249                 }
     150                        if ( is_wp_error( $responses[ $hash ] ) ) {
     151                                return $responses[ $hash ];
     152                        }
    250153
    251                 // Construct Cookie: header if any cookies are set.
    252                 self::buildCookieHeader( $r );
    253 
    254                 // Avoid issues where mbstring.func_overload is enabled.
    255                 mbstring_binary_safe_encoding();
    256 
    257                 if ( ! isset( $r['headers']['Accept-Encoding'] ) ) {
    258                         if ( $encoding = WP_Http_Encoding::accept_encoding( $url, $r ) )
    259                                 $r['headers']['Accept-Encoding'] = $encoding;
     154                        $this->add_cookies( $responses[ $hash ], $request );
     155                        return $responses[ $hash ];
    260156                }
    261157
    262                 if ( ( ! is_null( $r['body'] ) && '' != $r['body'] ) || 'POST' == $r['method'] || 'PUT' == $r['method'] ) {
    263                         if ( is_array( $r['body'] ) || is_object( $r['body'] ) ) {
    264                                 $r['body'] = http_build_query( $r['body'], null, '&' );
     158                $multi = $this->_dispatch_request( $requests );
    265159
    266                                 if ( ! isset( $r['headers']['Content-Type'] ) )
    267                                         $r['headers']['Content-Type'] = 'application/x-www-form-urlencoded; charset=' . get_option( 'blog_charset' );
     160                foreach ( $requests as $hash => $request ) {
     161                        $responses[ $hash ] = $multi[ $hash ];
     162                        if ( is_wp_error( $responses[ $hash ] ) ) {
     163                                continue;
    268164                        }
    269165
    270                         if ( '' === $r['body'] )
    271                                 $r['body'] = null;
    272 
    273                         if ( ! isset( $r['headers']['Content-Length'] ) && ! isset( $r['headers']['content-length'] ) )
    274                                 $r['headers']['Content-Length'] = strlen( $r['body'] );
     166                        $this->add_cookies( $responses[ $hash ], $request );
    275167                }
    276168
    277                 $response = $this->_dispatch_request( $url, $r );
    278 
    279169                reset_mbstring_encoding();
    280170
    281                 if ( is_wp_error( $response ) )
    282                         return $response;
    283 
    284                 // Append cookies that were used in this request to the response
    285                 if ( ! empty( $r['cookies'] ) ) {
    286                         $cookies_set = wp_list_pluck( $response['cookies'], 'name' );
    287                         foreach ( $r['cookies'] as $cookie ) {
    288                                 if ( ! in_array( $cookie->name, $cookies_set ) && $cookie->test( $url ) ) {
    289                                         $response['cookies'][] = $cookie;
    290                                 }
    291                         }
    292                 }
    293 
    294                 return $response;
     171                return $responses;
    295172        }
    296173
    297174        /**
     
    332209                return false;
    333210        }
    334211
    335         /**
    336          * Dispatches a HTTP request to a supporting transport.
    337          *
    338          * Tests each transport in order to find a transport which matches the request arguments.
    339          * Also caches the transport instance to be used later.
    340          *
    341          * The order for requests is cURL, and then PHP Streams.
    342          *
    343          * @since 3.2.0
    344          *
    345          * @static
    346          * @access private
    347          *
    348          * @param string $url URL to Request
    349          * @param array $args Request arguments
    350          * @return array|WP_Error Array containing 'headers', 'body', 'response', 'cookies', 'filename'. A WP_Error instance upon error
    351          */
    352         private function _dispatch_request( $url, $args ) {
    353                 static $transports = array();
     212        public function _get_first_available_multi_transport( $args, $url = null ) {
     213                /**
     214                 * Filter which HTTP transports are available and in what order.
     215                 *
     216                 * @since 4.4.0
     217                 *
     218                 * @param array  $value Array of HTTP transports to check. Default array contains
     219                 *                      'curl', and 'streams', in that order.
     220                 * @param array  $args  HTTP request arguments.
     221                 * @param string $url   The URL to request.
     222                 */
     223                $request_order = apply_filters( 'http_api_multi_transports', array( 'Curl' ), $args, $url );
    354224
    355                 $class = $this->_get_first_available_transport( $args, $url );
    356                 if ( !$class )
    357                         return new WP_Error( 'http_failure', __( 'There are no HTTP transports available which can complete the requested request.' ) );
     225                // Loop over each transport on each HTTP request looking for one which will serve this request's needs.
     226                foreach ( $request_order as $transport ) {
     227                        $class = 'WP_HTTP_' . $transport;
    358228
    359                 // Transport claims to support request, instantiate it and give it a whirl.
    360                 if ( empty( $transports[$class] ) )
    361                         $transports[$class] = new $class;
     229                        // Check to see if this transport is a possibility, calls the transport statically.
     230                        if ( !call_user_func( array( $class, 'test' ), $args, $url ) )
     231                                continue;
    362232
    363                 $response = $transports[$class]->request( $url, $args );
     233                        return $class;
     234                }
    364235
     236                return false;
     237        }
     238
     239        private function _filter_response( $response, $class, $args, $url ) {
    365240                /**
    366241                 * Fires after an HTTP API response is received and before the response is returned.
    367242                 *
     
    375250                 */
    376251                do_action( 'http_api_debug', $response, 'response', $class, $args, $url );
    377252
    378                 if ( is_wp_error( $response ) )
     253                if ( is_wp_error( $response ) ) {
    379254                        return $response;
     255                }
    380256
    381257                /**
    382258                 * Filter the HTTP API response immediately before the response is returned.
     
    391267        }
    392268
    393269        /**
     270         * Dispatches a HTTP request to a supporting transport.
     271         *
     272         * Tests each transport in order to find a transport which matches the request arguments.
     273         * Also caches the transport instance to be used later.
     274         *
     275         * The order for requests is cURL, and then PHP Streams.
     276         *
     277         * @since 3.2.0
     278         *
     279         * @static
     280         * @access private
     281         *
     282         * @param string $url URL to Request
     283         * @param array $args Request arguments
     284         * @return array|WP_Error Array containing 'headers', 'body', 'response', 'cookies', 'filename'. A WP_Error instance upon error
     285         */
     286        private function _dispatch_request( $url, $args = array() ) {
     287                static $transports = array();
     288                $requests = false;
     289
     290                if ( is_array( $url ) ) {
     291                        $requests = $url;
     292                        $class = $this->_get_first_available_multi_transport( $requests );
     293                } else {
     294                        $class = $this->_get_first_available_transport( $args, $url );
     295                }
     296
     297                if ( ! $class ) {
     298                        return new WP_Error( 'http_failure', __( 'There are no HTTP transports available which can complete the requested request.' ) );
     299                }
     300
     301                // Transport claims to support request, instantiate it and give it a whirl.
     302                if ( empty( $transports[$class] ) )
     303                        $transports[$class] = new $class;
     304
     305                if ( $requests ) {
     306                        $multi = $transports[$class]->request_multi( $requests );
     307                        foreach ( $requests as $hash => $request ) {
     308                                $multi[ $hash ] = $this->_filter_response( $multi[ $hash ], $class, $request->args, $request->url );
     309                        }
     310                        return $multi;
     311                }
     312
     313                $response = $transports[$class]->request( $url, $args );
     314                return $this->_filter_response( $response, $class, $args, $url );
     315        }
     316
     317        /**
    394318         * Uses the POST HTTP method.
    395319         *
    396320         * Used for sending data that is expected to be in the body.
     
    13161240         * @var string
    13171241         */
    13181242        private $headers = '';
     1243        private $multi_headers = array();
    13191244
    13201245        /**
    13211246         * Temporary body storage for during requests.
     
    13251250         * @var string
    13261251         */
    13271252        private $body = '';
     1253        private $multi_body = array();
    13281254
    13291255        /**
    13301256         * The maximum amount of data to receive from the remote server.
     
    13341260         * @var int
    13351261         */
    13361262        private $max_body_length = false;
     1263        private $multi_max_body_length = array();
    13371264
    13381265        /**
    13391266         * The file resource used for streaming to file.
     
    13431270         * @var resource
    13441271         */
    13451272        private $stream_handle = false;
     1273        private $multi_stream_handle = array();
    13461274
     1275        private $multi_handles = array();
     1276        private $multi_opts = array();
     1277
    13471278        /**
    13481279         * The total bytes written in the current request.
    13491280         *
     
    13521283         * @var int
    13531284         */
    13541285        private $bytes_written_total = 0;
     1286        private $multi_bytes_written_total = array();
    13551287
    1356         /**
    1357          * Send a HTTP request to a URI using cURL extension.
    1358          *
    1359          * @access public
    1360          * @since 2.7.0
    1361          *
    1362          * @param string $url The request URL.
    1363          * @param string|array $args Optional. Override the defaults.
    1364          * @return array|WP_Error Array containing 'headers', 'body', 'response', 'cookies', 'filename'. A WP_Error instance upon error
    1365          */
    1366         public function request($url, $args = array()) {
     1288        public function __call( $name, $arguments ) {
     1289                $header_prefix = '_stream_headers_for_';
     1290                if ( 0 === strpos( $name, $header_prefix ) ) {
     1291                        $hash = str_replace( $header_prefix, '', $name );
     1292                        if ( ! isset( $this->multi_headers[ $hash ] ) ) {
     1293                                $this->multi_headers[ $hash ] = '';
     1294                        }
     1295                        list( $handler, $headers ) = $arguments;
     1296                        $this->multi_headers[ $hash ] .= $headers;
     1297                        return strlen( $headers );
     1298                }
     1299
     1300                $body_prefix = '_stream_body_for_';
     1301                if ( 0 === strpos( $name, $body_prefix ) ) {
     1302                        $hash = str_replace( $body_prefix, '', $name );
     1303                        if ( ! isset( $this->multi_body[ $hash ] ) ) {
     1304                                $this->multi_body[ $hash ] = '';
     1305                        }
     1306                        list( $handler, $body ) = $arguments;
     1307                        $this->multi_stream_body( $hash, $body );
     1308                        return strlen( $body );
     1309                }
     1310        }
     1311
     1312        private function get_handle_with_opts( $url, $args ) {
    13671313                $defaults = array(
    13681314                        'method' => 'GET', 'timeout' => 5,
    13691315                        'redirection' => 5, 'httpversion' => '1.0',
    13701316                        'blocking' => true,
     1317                        'multi' => false,
    13711318                        'headers' => array(), 'body' => null, 'cookies' => array()
    13721319                );
    13731320
     
    14311378                 * a bug #17490 with redirected POST requests, so handle redirections outside Curl.
    14321379                 */
    14331380                curl_setopt( $handle, CURLOPT_FOLLOWLOCATION, false );
    1434                 if ( defined( 'CURLOPT_PROTOCOLS' ) ) // PHP 5.2.10 / cURL 7.19.4
     1381                // PHP 5.2.10 / cURL 7.19.4
     1382                if ( defined( 'CURLOPT_PROTOCOLS' ) ) {
    14351383                        curl_setopt( $handle, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS );
     1384                }
    14361385
    14371386                switch ( $r['method'] ) {
    14381387                        case 'HEAD':
     
    14481397                                break;
    14491398                        default:
    14501399                                curl_setopt( $handle, CURLOPT_CUSTOMREQUEST, $r['method'] );
    1451                                 if ( ! is_null( $r['body'] ) )
     1400                                if ( ! is_null( $r['body'] ) ) {
    14521401                                        curl_setopt( $handle, CURLOPT_POSTFIELDS, $r['body'] );
     1402                                }
    14531403                                break;
    14541404                }
    14551405
    1456                 if ( true === $r['blocking'] ) {
     1406                if ( $r['multi'] ) {
     1407                        curl_setopt( $handle, CURLOPT_HEADERFUNCTION, array( $this, '_stream_headers_for_' . $r['multi'] ) );
     1408                        curl_setopt( $handle, CURLOPT_WRITEFUNCTION, array( $this, '_stream_body_for_' . $r['multi'] ) );
     1409                } elseif ( true === $r['blocking'] ) {
    14571410                        curl_setopt( $handle, CURLOPT_HEADERFUNCTION, array( $this, 'stream_headers' ) );
    14581411                        curl_setopt( $handle, CURLOPT_WRITEFUNCTION, array( $this, 'stream_body' ) );
    14591412                }
     
    14601413
    14611414                curl_setopt( $handle, CURLOPT_HEADER, false );
    14621415
    1463                 if ( isset( $r['limit_response_size'] ) )
     1416                if ( !empty( $r['headers'] ) ) {
     1417                        // cURL expects full header strings in each element.
     1418                        $headers = array();
     1419                        foreach ( $r['headers'] as $name => $value ) {
     1420                                $headers[] = "{$name}: $value";
     1421                        }
     1422                        curl_setopt( $handle, CURLOPT_HTTPHEADER, $headers );
     1423                }
     1424
     1425                if ( $r['httpversion'] == '1.0' ) {
     1426                        curl_setopt( $handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0 );
     1427                } else {
     1428                        curl_setopt( $handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1 );
     1429                }
     1430
     1431                if ( $r['multi'] ) {
     1432                        curl_setopt( $handle, CURLOPT_ENCODING, 'identity' );
     1433
     1434                        $this->multi_handles[ $r['multi'] ] =& $handle;
     1435                        $this->multi_opts[ $r['multi'] ] =& $r;
     1436                }
     1437
     1438                return array( $handle, $r );
     1439        }
     1440
     1441        private function set_stream_options( $r ) {
     1442                if ( isset( $r['limit_response_size'] ) ) {
    14641443                        $this->max_body_length = intval( $r['limit_response_size'] );
    1465                 else
     1444                } else {
    14661445                        $this->max_body_length = false;
     1446                }
    14671447
    14681448                // If streaming to a file open a file handle, and setup our curl streaming handler.
    14691449                if ( $r['stream'] ) {
    1470                         if ( ! WP_DEBUG )
     1450                        if ( ! WP_DEBUG ) {
    14711451                                $this->stream_handle = @fopen( $r['filename'], 'w+' );
    1472                         else
     1452                        } else {
    14731453                                $this->stream_handle = fopen( $r['filename'], 'w+' );
    1474                         if ( ! $this->stream_handle )
     1454                        }
     1455
     1456                        if ( ! $this->stream_handle ) {
    14751457                                return new WP_Error( 'http_request_failed', sprintf( __( 'Could not open handle for fopen() to %s' ), $r['filename'] ) );
    1476                 } else {
    1477                         $this->stream_handle = false;
     1458                        }
    14781459                }
    14791460
    1480                 if ( !empty( $r['headers'] ) ) {
    1481                         // cURL expects full header strings in each element.
    1482                         $headers = array();
    1483                         foreach ( $r['headers'] as $name => $value ) {
    1484                                 $headers[] = "{$name}: $value";
     1461                $this->stream_handle = false;
     1462        }
     1463
     1464        private function set_multi_stream_options( $r ) {
     1465                $hash = $r['multi'];
     1466
     1467                if ( isset( $r['limit_response_size'] ) ) {
     1468                        $this->multi_max_body_length[ $hash ] = intval( $r['limit_response_size'] );
     1469                }
     1470
     1471                // If streaming to a file open a file handle, and setup our curl streaming handler.
     1472                if ( $r['stream'] ) {
     1473                        if ( ! WP_DEBUG ) {
     1474                                $this->multi_stream_handle[ $hash ] = @fopen( $r['filename'], 'w+' );
     1475                        } else {
     1476                                $this->multi_stream_handle[ $hash ] = fopen( $r['filename'], 'w+' );
    14851477                        }
    1486                         curl_setopt( $handle, CURLOPT_HTTPHEADER, $headers );
     1478
     1479                        if ( ! $this->multi_stream_handle[ $hash ] ) {
     1480                                return new WP_Error( 'http_request_failed', sprintf( __( 'Could not open handle for fopen() to %s' ), $r['filename'] ) );
     1481                        }
    14871482                }
    14881483
    1489                 if ( $r['httpversion'] == '1.0' )
    1490                         curl_setopt( $handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0 );
    1491                 else
    1492                         curl_setopt( $handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1 );
     1484                $this->multi_stream_handle[ $hash ] = false;
     1485        }
    14931486
     1487        /**
     1488         * Send a HTTP request to a URI using cURL extension.
     1489         *
     1490         * @access public
     1491         * @since 2.7.0
     1492         *
     1493         * @param string $url The request URL.
     1494         * @param string|array $args Optional. Override the defaults.
     1495         * @return array|WP_Error Array containing 'headers', 'body', 'response', 'cookies', 'filename'. A WP_Error instance upon error
     1496         */
     1497        public function request($url, $args = array()) {
     1498                list( $handle, $r ) = $this->get_handle_with_opts( $url, $args );
     1499                $stream_options = $this->set_stream_options( $r );
     1500                if ( is_wp_error( $stream_options ) ) {
     1501                        return $stream_options;
     1502                }
     1503
    14941504                /**
    14951505                 * Fires before the cURL request is executed.
    14961506                 *
     
    15831593                return $response;
    15841594        }
    15851595
     1596        public function request_multi( $requests ) {
     1597                $mh = curl_multi_init();
     1598
     1599                foreach ( $requests as $hash => $request ) {
     1600                        $request->args['multi'] = $hash;
     1601                        $this->get_handle_with_opts( $request->url, $request->args );
     1602                        $stream_options = $this->set_multi_stream_options( $this->multi_opts[ $hash ] );
     1603                        if ( is_wp_error( $stream_options ) ) {
     1604                                $responses[ $hash ] = $stream_options;
     1605                                continue;
     1606                        }
     1607
     1608                        /** This filter is documented in wp-includes/wp-http.php */
     1609                        do_action_ref_array( 'http_api_curl', array(
     1610                                &$this->multi_handles[ $hash ],
     1611                                 $this->multi_opts[ $hash ],
     1612                                $request->url
     1613                        ) );
     1614
     1615                        curl_multi_add_handle( $mh, $this->multi_handles[ $hash ] );
     1616                }
     1617
     1618                $active = null;
     1619
     1620                do {
     1621                        $mrc = curl_multi_exec($mh, $active);
     1622                } while ($mrc == CURLM_CALL_MULTI_PERFORM || $active);
     1623
     1624                $responses = array();
     1625                foreach ( $this->multi_handles as $hash => &$handle ) {
     1626                        $url = $requests[ $hash ]->url;
     1627                        $r = $this->multi_opts[ $hash ];
     1628                        $headers = WP_Http::processHeaders( $this->multi_headers[ $hash ], $url );
     1629                        $body = $this->multi_body[ $hash ];
     1630                        $bytes_written_total = $this->multi_bytes_written_total[ $hash ];
     1631
     1632                        $this->multi_headers[ $hash ] = '';
     1633                        $this->multi_body[ $hash ] = '';
     1634                        $this->multi_bytes_written_total[ $hash ] = 0;
     1635
     1636                        $curl_error = curl_errno( $handle );
     1637
     1638                        // If an error occurred, or, no response.
     1639                        if ( $curl_error || ( 0 === strlen( $body ) && empty( $headers['headers'] ) ) ) {
     1640                                if ( CURLE_WRITE_ERROR /* 23 */ == $curl_error ) {
     1641                                        if ( ! $this->multi_max_body_length[ $hash ]
     1642                                                || $this->multi_max_body_length[ $hash ] != $bytes_written_total ) {
     1643                                                if ( $r['stream'] ) {
     1644                                                        curl_multi_remove_handle( $mh, $handle );
     1645                                                        fclose( $this->stream_handle );
     1646                                                        $responses[ $hash ] = new WP_Error( 'http_request_failed', __( 'Failed to write request to temporary file.' ) );
     1647                                                } else {
     1648                                                        curl_multi_remove_handle( $mh, $handle );
     1649                                                        $responses[ $hash ] = new WP_Error( 'http_request_failed', curl_error( $handle ) );
     1650                                                }
     1651                                                continue;
     1652                                        }
     1653                                } else {
     1654                                        $curl_error = curl_error( $handle );
     1655                                        if ( $curl_error ) {
     1656                                                curl_multi_remove_handle( $mh, $handle );
     1657                                                $responses[ $hash ] = new WP_Error( 'http_request_failed', $curl_error );
     1658                                                continue;
     1659                                        }
     1660                                }
     1661
     1662                                if ( in_array( curl_getinfo( $handle, CURLINFO_HTTP_CODE ), array( 301, 302 ) ) ) {
     1663                                        curl_multi_remove_handle( $mh, $handle );
     1664                                        $responses[ $hash ] = new WP_Error( 'http_request_failed', __( 'Too many redirects.' ) );
     1665                                        continue;
     1666                                }
     1667                        }
     1668
     1669                        curl_multi_remove_handle( $mh, $handle );
     1670
     1671                        if ( $r['stream'] ) {
     1672                                fclose( $this->multi_stream_handle[ $hash ] );
     1673                        }
     1674
     1675                        $response = array(
     1676                                'headers' => $headers['headers'],
     1677                                'body' => null,
     1678                                'response' => $headers['response'],
     1679                                'cookies' => $headers['cookies'],
     1680                                'filename' => $r['filename']
     1681                        );
     1682
     1683                        // Handle redirects.
     1684                        if ( false !== ( $redirect_response = WP_HTTP::handle_redirects( $url, $r, $response ) ) ) {
     1685                                $responses[ $hash ] = $redirect_response;
     1686                                continue;
     1687                        }
     1688
     1689                        if ( true === $r['decompress'] && true === WP_Http_Encoding::should_decode( $headers['headers'] ) ) {
     1690                                $body = WP_Http_Encoding::decompress( $body );
     1691                        }
     1692
     1693                        $response['body'] = $body;
     1694                        $responses[ $hash ] = $response;
     1695                }
     1696
     1697                curl_multi_close( $mh );
     1698
     1699                return $responses;
     1700        }
     1701
    15861702        /**
    15871703         * Grab the headers of the cURL request
    15881704         *
     
    16281744                return $bytes_written;
    16291745        }
    16301746
     1747        private function multi_stream_body( $hash, $data ) {
     1748                $data_length = strlen( $data );
     1749
     1750                if ( ! isset( $this->multi_bytes_written_total[ $hash ] ) ) {
     1751                        $this->multi_bytes_written_total[ $hash ] = 0;
     1752                }
     1753                $total = $this->multi_bytes_written_total[ $hash ];
     1754                if ( ! isset( $this->multi_max_body_length[ $hash ] ) ) {
     1755                        $this->multi_max_body_length[ $hash ] = 0;
     1756                }
     1757                $max = $this->multi_max_body_length[ $hash ];
     1758                if ( $max && ( $total + $data_length ) > $max ) {
     1759                        $data_length = ( $max - $total );
     1760                        $data = substr( $data, 0, $data_length );
     1761                }
     1762
     1763                if ( isset( $this->multi_stream_handle[ $hash ] ) && $this->multi_stream_handle[ $hash ] ) {
     1764                        $bytes_written = fwrite( $this->multi_stream_handle[ $hash ], $data );
     1765                } else {
     1766                        $this->multi_body[ $hash ] .= $data;
     1767                        $bytes_written = $data_length;
     1768                }
     1769
     1770                $this->multi_bytes_written_total[ $hash ] += $bytes_written;
     1771
     1772                // Upon event of this function returning less than strlen( $data ) curl will error with CURLE_WRITE_ERROR.
     1773                return $bytes_written;
     1774        }
     1775
    16311776        /**
    16321777         * Whether this class can be used for retrieving an URL.
    16331778         *
     
    23042449                return ( function_exists('gzuncompress') || function_exists('gzdeflate') || function_exists('gzinflate') );
    23052450        }
    23062451}
     2452
     2453class WP_Http_Request {
     2454        public $url;
     2455        public $args;
     2456        public $defaults;
     2457
     2458        public $pre = false;
     2459
     2460        /**
     2461         *
     2462         * @global string $wp_version
     2463         *
     2464         * @param string       $url  The request URL.
     2465         * @param string|array $args {
     2466         *     Optional. Array or string of HTTP request arguments.
     2467         *
     2468         *     @type string       $method              Request method. Accepts 'GET', 'POST', 'HEAD', or 'PUT'.
     2469         *                                             Some transports technically allow others, but should not be
     2470         *                                             assumed. Default 'GET'.
     2471         *     @type int          $timeout             How long the connection should stay open in seconds. Default 5.
     2472         *     @type int          $redirection         Number of allowed redirects. Not supported by all transports
     2473         *                                             Default 5.
     2474         *     @type string       $httpversion         Version of the HTTP protocol to use. Accepts '1.0' and '1.1'.
     2475         *                                             Default '1.0'.
     2476         *     @type string       $user-agent          User-agent value sent.
     2477         *                                             Default WordPress/' . $wp_version . '; ' . get_bloginfo( 'url' ).
     2478         *     @type bool         $reject_unsafe_urls  Whether to pass URLs through {@see wp_http_validate_url()}.
     2479         *                                             Default false.
     2480         *     @type bool         $blocking            Whether the calling code requires the result of the request.
     2481         *                                             If set to false, the request will be sent to the remote server,
     2482         *                                             and processing returned to the calling code immediately, the caller
     2483         *                                             will know if the request succeeded or failed, but will not receive
     2484         *                                             any response from the remote server. Default true.
     2485         *     @type string|array $headers             Array or string of headers to send with the request.
     2486         *                                             Default empty array.
     2487         *     @type array        $cookies             List of cookies to send with the request. Default empty array.
     2488         *     @type string|array $body                Body to send with the request. Default null.
     2489         *     @type bool         $compress            Whether to compress the $body when sending the request.
     2490         *                                             Default false.
     2491         *     @type bool         $decompress          Whether to decompress a compressed response. If set to false and
     2492         *                                             compressed content is returned in the response anyway, it will
     2493         *                                             need to be separately decompressed. Default true.
     2494         *     @type bool         $sslverify           Whether to verify SSL for the request. Default true.
     2495         *     @type string       sslcertificates      Absolute path to an SSL certificate .crt file.
     2496         *                                             Default ABSPATH . WPINC . '/certificates/ca-bundle.crt'.
     2497         *     @type bool         $stream              Whether to stream to a file. If set to true and no filename was
     2498         *                                             given, it will be droped it in the WP temp dir and its name will
     2499         *                                             be set using the basename of the URL. Default false.
     2500         *     @type string       $filename            Filename of the file to write to when streaming. $stream must be
     2501         *                                             set to true. Default null.
     2502         *     @type int          $limit_response_size Size in bytes to limit the response to. Default null.
     2503         *
     2504         * }
     2505         */
     2506        public function __construct( $url, $args = array() ) {
     2507                global $wp_version;
     2508
     2509                $this->url = $url;
     2510
     2511                $this->defaults = array(
     2512                        'method' => 'GET',
     2513                        /**
     2514                         * Filter the timeout value for an HTTP request.
     2515                         *
     2516                         * @since 2.7.0
     2517                         *
     2518                         * @param int $timeout_value Time in seconds until a request times out.
     2519                         *                           Default 5.
     2520                         */
     2521                        'timeout' => apply_filters( 'http_request_timeout', 5 ),
     2522                        /**
     2523                         * Filter the number of redirects allowed during an HTTP request.
     2524                         *
     2525                         * @since 2.7.0
     2526                         *
     2527                         * @param int $redirect_count Number of redirects allowed. Default 5.
     2528                         */
     2529                        'redirection' => apply_filters( 'http_request_redirection_count', 5 ),
     2530                        /**
     2531                         * Filter the version of the HTTP protocol used in a request.
     2532                         *
     2533                         * @since 2.7.0
     2534                         *
     2535                         * @param string $version Version of HTTP used. Accepts '1.0' and '1.1'.
     2536                         *                        Default '1.0'.
     2537                         */
     2538                        'httpversion' => apply_filters( 'http_request_version', '1.0' ),
     2539                        /**
     2540                         * Filter the user agent value sent with an HTTP request.
     2541                         *
     2542                         * @since 2.7.0
     2543                         *
     2544                         * @param string $user_agent WordPress user agent string.
     2545                         */
     2546                        'user-agent' => apply_filters( 'http_headers_useragent', 'WordPress/' . $wp_version . '; ' . get_bloginfo( 'url' ) ),
     2547                        /**
     2548                         * Filter whether to pass URLs through wp_http_validate_url() in an HTTP request.
     2549                         *
     2550                         * @since 3.6.0
     2551                         *
     2552                         * @param bool $pass_url Whether to pass URLs through wp_http_validate_url().
     2553                         *                       Default false.
     2554                         */
     2555                        'reject_unsafe_urls' => apply_filters( 'http_request_reject_unsafe_urls', false ),
     2556                        'blocking' => true,
     2557                        'headers' => array(),
     2558                        'cookies' => array(),
     2559                        'body' => null,
     2560                        'compress' => false,
     2561                        'decompress' => true,
     2562                        'sslverify' => true,
     2563                        'sslcertificates' => ABSPATH . WPINC . '/certificates/ca-bundle.crt',
     2564                        'stream' => false,
     2565                        'filename' => null,
     2566                        'limit_response_size' => null,
     2567                );
     2568
     2569                // Pre-parse for the HEAD checks.
     2570                $args = wp_parse_args( $args );
     2571
     2572                // By default, Head requests do not cause redirections.
     2573                if ( isset($args['method']) && 'HEAD' == $args['method'] ) {
     2574                        $defaults['redirection'] = 0;
     2575                }
     2576
     2577                $r = wp_parse_args( $args, $this->defaults );
     2578                /**
     2579                 * Filter the arguments used in an HTTP request.
     2580                 *
     2581                 * @since 2.7.0
     2582                 *
     2583                 * @param array  $r   An array of HTTP request arguments.
     2584                 * @param string $url The request URL.
     2585                 */
     2586                $this->args = apply_filters( 'http_request_args', $r, $url );
     2587
     2588                // The transports decrement this, store a copy of the original value for loop purposes.
     2589                if ( ! isset( $this->args['_redirection'] ) )
     2590                        $this->args['_redirection'] = $this->args['redirection'];
     2591
     2592                /**
     2593                 * Filter whether to preempt an HTTP request's return.
     2594                 *
     2595                 * Returning a truthy value to the filter will short-circuit
     2596                 * the HTTP request and return early with that value.
     2597                 *
     2598                 * @since 2.9.0
     2599                 *
     2600                 * @param bool   $preempt Whether to preempt an HTTP request return. Default false.
     2601                 * @param array  $r       HTTP request arguments.
     2602                 * @param string $url     The request URL.
     2603                 */
     2604                $this->pre = apply_filters( 'pre_http_request', false, $this->args, $this->url );
     2605                if ( false !== $this->pre ) {
     2606                        $this->parse();
     2607                }
     2608        }
     2609
     2610        protected function parse() {
     2611                if ( function_exists( 'wp_kses_bad_protocol' ) ) {
     2612                        if ( $this->args['reject_unsafe_urls'] ) {
     2613                                $this->url = wp_http_validate_url( $this->url );
     2614                        }
     2615
     2616                        if ( $this->url ) {
     2617                                $this->url = wp_kses_bad_protocol( $this->url, array( 'http', 'https', 'ssl' ) );
     2618                        }
     2619                }
     2620
     2621                $arrURL = @parse_url( $this->url );
     2622
     2623                if ( empty( $this->url ) || empty( $arrURL['scheme'] ) ) {
     2624                        return new WP_Error('http_request_failed', __('A valid URL was not provided.'));
     2625                }
     2626
     2627                if ( $this->block_request( $this->url ) ) {
     2628                        return new WP_Error( 'http_request_failed', __( 'User has blocked requests through HTTP.' ) );
     2629                }
     2630                /*
     2631                 * Determine if this is a https call and pass that on to the transport functions
     2632                 * so that we can blacklist the transports that do not support ssl verification
     2633                 */
     2634                $this->args['ssl'] = $arrURL['scheme'] == 'https' || $arrURL['scheme'] == 'ssl';
     2635
     2636                // Determine if this request is to OUR install of WordPress.
     2637                $homeURL = parse_url( get_bloginfo( 'url' ) );
     2638                $this->args['local'] = 'localhost' == $arrURL['host'] || ( isset( $homeURL['host'] ) && $homeURL['host'] == $arrURL['host'] );
     2639                unset( $homeURL );
     2640
     2641                /*
     2642                 * If we are streaming to a file but no filename was given drop it in the WP temp dir
     2643                 * and pick its name using the basename of the $url.
     2644                 */
     2645                if ( $this->args['stream']  && empty( $this->args['filename'] ) ) {
     2646                        $this->args['filename'] = get_temp_dir() . wp_unique_filename( get_temp_dir(), basename( $this->url ) );
     2647                }
     2648
     2649                /*
     2650                 * Force some settings if we are streaming to a file and check for existence and perms
     2651                 * of destination directory.
     2652                 */
     2653                if ( $this->args['stream'] ) {
     2654                        $this->args['blocking'] = true;
     2655                        if ( ! wp_is_writable( dirname( $this->args['filename'] ) ) ) {
     2656                                return new WP_Error( 'http_request_failed', __( 'Destination directory for file streaming does not exist or is not writable.' ) );
     2657                        }
     2658                }
     2659
     2660                if ( is_null( $this->args['headers'] ) ) {
     2661                        $this->args['headers'] = array();
     2662                }
     2663
     2664                if ( ! is_array( $this->args['headers'] ) ) {
     2665                        $processedHeaders = WP_Http::processHeaders( $this->args['headers'], $this->url );
     2666                        $this->args['headers'] = $processedHeaders['headers'];
     2667                }
     2668
     2669                if ( isset( $this->args['headers']['User-Agent'] ) ) {
     2670                        $this->args['user-agent'] = $this->args['headers']['User-Agent'];
     2671                        unset( $this->args['headers']['User-Agent'] );
     2672                }
     2673
     2674                if ( isset( $this->args['headers']['user-agent'] ) ) {
     2675                        $this->args['user-agent'] = $this->args['headers']['user-agent'];
     2676                        unset( $this->args['headers']['user-agent'] );
     2677                }
     2678
     2679                if ( '1.1' == $this->args['httpversion'] && !isset( $this->args['headers']['connection'] ) ) {
     2680                        $this->args['headers']['connection'] = 'close';
     2681                }
     2682
     2683                // Construct Cookie: header if any cookies are set.
     2684                WP_Http::buildCookieHeader( $this->url );
     2685
     2686                // Avoid issues where mbstring.func_overload is enabled.
     2687                mbstring_binary_safe_encoding();
     2688
     2689                if ( ! isset( $this->args['headers']['Accept-Encoding'] ) ) {
     2690                        $encoding = WP_Http_Encoding::accept_encoding( $this->url, $this->args );
     2691                        if ( $encoding ) {
     2692                                $this->args['headers']['Accept-Encoding'] = $encoding;
     2693                        }
     2694                }
     2695
     2696                if ( ( ! is_null( $this->args['body'] ) && '' != $this->args['body'] ) || 'POST' == $this->args['method'] || 'PUT' == $this->args['method'] ) {
     2697                        if ( is_array( $this->args['body'] ) || is_object( $this->args['body'] ) ) {
     2698                                $this->args['body'] = http_build_query( $this->args['body'], null, '&' );
     2699
     2700                                if ( ! isset( $this->args['headers']['Content-Type'] ) ) {
     2701                                        $this->args['headers']['Content-Type'] = 'application/x-www-form-urlencoded; charset=' . get_option( 'blog_charset' );
     2702                                }
     2703                        }
     2704
     2705                        if ( '' === $this->args['body'] ) {
     2706                                $this->args['body'] = null;
     2707                        }
     2708
     2709                        if ( ! isset( $this->args['headers']['Content-Length'] ) && ! isset( $this->args['headers']['content-length'] ) ) {
     2710                                $this->args['headers']['Content-Length'] = strlen( $this->args['body'] );
     2711                        }
     2712                }
     2713        }
     2714}
     2715 No newline at end of file