Ticket #33055: 33055.diff
File 33055.diff, 36.1 KB (added by , 10 years ago) |
---|
-
src/wp-includes/class-http.php
83 83 * A WP_Error instance upon error. 84 84 */ 85 85 public function request( $url, $args = array() ) { 86 global $wp_version; 86 if ( is_array( $url ) ) { 87 return $this->request_multi( $url ); 88 } 87 89 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 } 145 94 146 // Pre-parse for the HEAD checks. 147 $args = wp_parse_args( $args ); 95 $response = $this->_dispatch_request( $request->url, $request->args ); 148 96 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(); 152 98 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; 190 101 } 191 102 192 $ arrURL = @parse_url( $url);103 $this->add_cookies( $response, $request ); 193 104 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 } 196 107 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 } 199 111 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; 217 116 } 218 117 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 } 227 123 } 124 } 228 125 229 if ( is_null( $r['headers'] ) ) 230 $r['headers'] = array(); 126 public function request_multi( $tuples ) { 127 $requests = array(); 128 $responses = array(); 231 129 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 } 235 139 } 236 140 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; 240 144 } 241 145 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(); 246 149 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 } 250 153 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 ]; 260 156 } 261 157 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 ); 265 159 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; 268 164 } 269 165 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 ); 275 167 } 276 168 277 $response = $this->_dispatch_request( $url, $r );278 279 169 reset_mbstring_encoding(); 280 170 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; 295 172 } 296 173 297 174 /** … … 332 209 return false; 333 210 } 334 211 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 ); 354 224 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; 358 228 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; 362 232 363 $response = $transports[$class]->request( $url, $args ); 233 return $class; 234 } 364 235 236 return false; 237 } 238 239 private function _filter_response( $response, $class, $args, $url ) { 365 240 /** 366 241 * Fires after an HTTP API response is received and before the response is returned. 367 242 * … … 375 250 */ 376 251 do_action( 'http_api_debug', $response, 'response', $class, $args, $url ); 377 252 378 if ( is_wp_error( $response ) ) 253 if ( is_wp_error( $response ) ) { 379 254 return $response; 255 } 380 256 381 257 /** 382 258 * Filter the HTTP API response immediately before the response is returned. … … 391 267 } 392 268 393 269 /** 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 /** 394 318 * Uses the POST HTTP method. 395 319 * 396 320 * Used for sending data that is expected to be in the body. … … 1316 1240 * @var string 1317 1241 */ 1318 1242 private $headers = ''; 1243 private $multi_headers = array(); 1319 1244 1320 1245 /** 1321 1246 * Temporary body storage for during requests. … … 1325 1250 * @var string 1326 1251 */ 1327 1252 private $body = ''; 1253 private $multi_body = array(); 1328 1254 1329 1255 /** 1330 1256 * The maximum amount of data to receive from the remote server. … … 1334 1260 * @var int 1335 1261 */ 1336 1262 private $max_body_length = false; 1263 private $multi_max_body_length = array(); 1337 1264 1338 1265 /** 1339 1266 * The file resource used for streaming to file. … … 1343 1270 * @var resource 1344 1271 */ 1345 1272 private $stream_handle = false; 1273 private $multi_stream_handle = array(); 1346 1274 1275 private $multi_handles = array(); 1276 private $multi_opts = array(); 1277 1347 1278 /** 1348 1279 * The total bytes written in the current request. 1349 1280 * … … 1352 1283 * @var int 1353 1284 */ 1354 1285 private $bytes_written_total = 0; 1286 private $multi_bytes_written_total = array(); 1355 1287 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 ) { 1367 1313 $defaults = array( 1368 1314 'method' => 'GET', 'timeout' => 5, 1369 1315 'redirection' => 5, 'httpversion' => '1.0', 1370 1316 'blocking' => true, 1317 'multi' => false, 1371 1318 'headers' => array(), 'body' => null, 'cookies' => array() 1372 1319 ); 1373 1320 … … 1431 1378 * a bug #17490 with redirected POST requests, so handle redirections outside Curl. 1432 1379 */ 1433 1380 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' ) ) { 1435 1383 curl_setopt( $handle, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS ); 1384 } 1436 1385 1437 1386 switch ( $r['method'] ) { 1438 1387 case 'HEAD': … … 1448 1397 break; 1449 1398 default: 1450 1399 curl_setopt( $handle, CURLOPT_CUSTOMREQUEST, $r['method'] ); 1451 if ( ! is_null( $r['body'] ) ) 1400 if ( ! is_null( $r['body'] ) ) { 1452 1401 curl_setopt( $handle, CURLOPT_POSTFIELDS, $r['body'] ); 1402 } 1453 1403 break; 1454 1404 } 1455 1405 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'] ) { 1457 1410 curl_setopt( $handle, CURLOPT_HEADERFUNCTION, array( $this, 'stream_headers' ) ); 1458 1411 curl_setopt( $handle, CURLOPT_WRITEFUNCTION, array( $this, 'stream_body' ) ); 1459 1412 } … … 1460 1413 1461 1414 curl_setopt( $handle, CURLOPT_HEADER, false ); 1462 1415 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'] ) ) { 1464 1443 $this->max_body_length = intval( $r['limit_response_size'] ); 1465 else1444 } else { 1466 1445 $this->max_body_length = false; 1446 } 1467 1447 1468 1448 // If streaming to a file open a file handle, and setup our curl streaming handler. 1469 1449 if ( $r['stream'] ) { 1470 if ( ! WP_DEBUG ) 1450 if ( ! WP_DEBUG ) { 1471 1451 $this->stream_handle = @fopen( $r['filename'], 'w+' ); 1472 else1452 } else { 1473 1453 $this->stream_handle = fopen( $r['filename'], 'w+' ); 1474 if ( ! $this->stream_handle ) 1454 } 1455 1456 if ( ! $this->stream_handle ) { 1475 1457 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 } 1478 1459 } 1479 1460 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+' ); 1485 1477 } 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 } 1487 1482 } 1488 1483 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 } 1493 1486 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 1494 1504 /** 1495 1505 * Fires before the cURL request is executed. 1496 1506 * … … 1583 1593 return $response; 1584 1594 } 1585 1595 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 1586 1702 /** 1587 1703 * Grab the headers of the cURL request 1588 1704 * … … 1628 1744 return $bytes_written; 1629 1745 } 1630 1746 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 1631 1776 /** 1632 1777 * Whether this class can be used for retrieving an URL. 1633 1778 * … … 2304 2449 return ( function_exists('gzuncompress') || function_exists('gzdeflate') || function_exists('gzinflate') ); 2305 2450 } 2306 2451 } 2452 2453 class 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