[ Index ] |
PHP Cross Reference of WordPress |
[Summary view] [Print] [Text view]
1 <?php 2 /** 3 * HTTP API: WP_Http_Curl class 4 * 5 * @package WordPress 6 * @subpackage HTTP 7 * @since 4.4.0 8 */ 9 10 /** 11 * Core class used to integrate Curl as an HTTP transport. 12 * 13 * HTTP request method uses Curl extension to retrieve the url. 14 * 15 * Requires the Curl extension to be installed. 16 * 17 * @since 2.7.0 18 */ 19 class WP_Http_Curl { 20 21 /** 22 * Temporary header storage for during requests. 23 * 24 * @since 3.2.0 25 * @var string 26 */ 27 private $headers = ''; 28 29 /** 30 * Temporary body storage for during requests. 31 * 32 * @since 3.6.0 33 * @var string 34 */ 35 private $body = ''; 36 37 /** 38 * The maximum amount of data to receive from the remote server. 39 * 40 * @since 3.6.0 41 * @var int|false 42 */ 43 private $max_body_length = false; 44 45 /** 46 * The file resource used for streaming to file. 47 * 48 * @since 3.6.0 49 * @var resource|false 50 */ 51 private $stream_handle = false; 52 53 /** 54 * The total bytes written in the current request. 55 * 56 * @since 4.1.0 57 * @var int 58 */ 59 private $bytes_written_total = 0; 60 61 /** 62 * Send a HTTP request to a URI using cURL extension. 63 * 64 * @since 2.7.0 65 * 66 * @param string $url The request URL. 67 * @param string|array $args Optional. Override the defaults. 68 * @return array|WP_Error Array containing 'headers', 'body', 'response', 'cookies', 'filename'. A WP_Error instance upon error 69 */ 70 public function request( $url, $args = array() ) { 71 $defaults = array( 72 'method' => 'GET', 73 'timeout' => 5, 74 'redirection' => 5, 75 'httpversion' => '1.0', 76 'blocking' => true, 77 'headers' => array(), 78 'body' => null, 79 'cookies' => array(), 80 ); 81 82 $parsed_args = wp_parse_args( $args, $defaults ); 83 84 if ( isset( $parsed_args['headers']['User-Agent'] ) ) { 85 $parsed_args['user-agent'] = $parsed_args['headers']['User-Agent']; 86 unset( $parsed_args['headers']['User-Agent'] ); 87 } elseif ( isset( $parsed_args['headers']['user-agent'] ) ) { 88 $parsed_args['user-agent'] = $parsed_args['headers']['user-agent']; 89 unset( $parsed_args['headers']['user-agent'] ); 90 } 91 92 // Construct Cookie: header if any cookies are set. 93 WP_Http::buildCookieHeader( $parsed_args ); 94 95 $handle = curl_init(); 96 97 // cURL offers really easy proxy support. 98 $proxy = new WP_HTTP_Proxy(); 99 100 if ( $proxy->is_enabled() && $proxy->send_through_proxy( $url ) ) { 101 102 curl_setopt( $handle, CURLOPT_PROXYTYPE, CURLPROXY_HTTP ); 103 curl_setopt( $handle, CURLOPT_PROXY, $proxy->host() ); 104 curl_setopt( $handle, CURLOPT_PROXYPORT, $proxy->port() ); 105 106 if ( $proxy->use_authentication() ) { 107 curl_setopt( $handle, CURLOPT_PROXYAUTH, CURLAUTH_ANY ); 108 curl_setopt( $handle, CURLOPT_PROXYUSERPWD, $proxy->authentication() ); 109 } 110 } 111 112 $is_local = isset( $parsed_args['local'] ) && $parsed_args['local']; 113 $ssl_verify = isset( $parsed_args['sslverify'] ) && $parsed_args['sslverify']; 114 if ( $is_local ) { 115 /** This filter is documented in wp-includes/class-wp-http-streams.php */ 116 $ssl_verify = apply_filters( 'https_local_ssl_verify', $ssl_verify, $url ); 117 } elseif ( ! $is_local ) { 118 /** This filter is documented in wp-includes/class-wp-http.php */ 119 $ssl_verify = apply_filters( 'https_ssl_verify', $ssl_verify, $url ); 120 } 121 122 /* 123 * CURLOPT_TIMEOUT and CURLOPT_CONNECTTIMEOUT expect integers. Have to use ceil since. 124 * a value of 0 will allow an unlimited timeout. 125 */ 126 $timeout = (int) ceil( $parsed_args['timeout'] ); 127 curl_setopt( $handle, CURLOPT_CONNECTTIMEOUT, $timeout ); 128 curl_setopt( $handle, CURLOPT_TIMEOUT, $timeout ); 129 130 curl_setopt( $handle, CURLOPT_URL, $url ); 131 curl_setopt( $handle, CURLOPT_RETURNTRANSFER, true ); 132 curl_setopt( $handle, CURLOPT_SSL_VERIFYHOST, ( true === $ssl_verify ) ? 2 : false ); 133 curl_setopt( $handle, CURLOPT_SSL_VERIFYPEER, $ssl_verify ); 134 135 if ( $ssl_verify ) { 136 curl_setopt( $handle, CURLOPT_CAINFO, $parsed_args['sslcertificates'] ); 137 } 138 139 curl_setopt( $handle, CURLOPT_USERAGENT, $parsed_args['user-agent'] ); 140 141 /* 142 * The option doesn't work with safe mode or when open_basedir is set, and there's 143 * a bug #17490 with redirected POST requests, so handle redirections outside Curl. 144 */ 145 curl_setopt( $handle, CURLOPT_FOLLOWLOCATION, false ); 146 curl_setopt( $handle, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS ); 147 148 switch ( $parsed_args['method'] ) { 149 case 'HEAD': 150 curl_setopt( $handle, CURLOPT_NOBODY, true ); 151 break; 152 case 'POST': 153 curl_setopt( $handle, CURLOPT_POST, true ); 154 curl_setopt( $handle, CURLOPT_POSTFIELDS, $parsed_args['body'] ); 155 break; 156 case 'PUT': 157 curl_setopt( $handle, CURLOPT_CUSTOMREQUEST, 'PUT' ); 158 curl_setopt( $handle, CURLOPT_POSTFIELDS, $parsed_args['body'] ); 159 break; 160 default: 161 curl_setopt( $handle, CURLOPT_CUSTOMREQUEST, $parsed_args['method'] ); 162 if ( ! is_null( $parsed_args['body'] ) ) { 163 curl_setopt( $handle, CURLOPT_POSTFIELDS, $parsed_args['body'] ); 164 } 165 break; 166 } 167 168 if ( true === $parsed_args['blocking'] ) { 169 curl_setopt( $handle, CURLOPT_HEADERFUNCTION, array( $this, 'stream_headers' ) ); 170 curl_setopt( $handle, CURLOPT_WRITEFUNCTION, array( $this, 'stream_body' ) ); 171 } 172 173 curl_setopt( $handle, CURLOPT_HEADER, false ); 174 175 if ( isset( $parsed_args['limit_response_size'] ) ) { 176 $this->max_body_length = (int) $parsed_args['limit_response_size']; 177 } else { 178 $this->max_body_length = false; 179 } 180 181 // If streaming to a file open a file handle, and setup our curl streaming handler. 182 if ( $parsed_args['stream'] ) { 183 if ( ! WP_DEBUG ) { 184 $this->stream_handle = @fopen( $parsed_args['filename'], 'w+' ); 185 } else { 186 $this->stream_handle = fopen( $parsed_args['filename'], 'w+' ); 187 } 188 if ( ! $this->stream_handle ) { 189 return new WP_Error( 190 'http_request_failed', 191 sprintf( 192 /* translators: 1: fopen(), 2: File name. */ 193 __( 'Could not open handle for %1$s to %2$s.' ), 194 'fopen()', 195 $parsed_args['filename'] 196 ) 197 ); 198 } 199 } else { 200 $this->stream_handle = false; 201 } 202 203 if ( ! empty( $parsed_args['headers'] ) ) { 204 // cURL expects full header strings in each element. 205 $headers = array(); 206 foreach ( $parsed_args['headers'] as $name => $value ) { 207 $headers[] = "{$name}: $value"; 208 } 209 curl_setopt( $handle, CURLOPT_HTTPHEADER, $headers ); 210 } 211 212 if ( '1.0' === $parsed_args['httpversion'] ) { 213 curl_setopt( $handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0 ); 214 } else { 215 curl_setopt( $handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1 ); 216 } 217 218 /** 219 * Fires before the cURL request is executed. 220 * 221 * Cookies are not currently handled by the HTTP API. This action allows 222 * plugins to handle cookies themselves. 223 * 224 * @since 2.8.0 225 * 226 * @param resource $handle The cURL handle returned by curl_init() (passed by reference). 227 * @param array $parsed_args The HTTP request arguments. 228 * @param string $url The request URL. 229 */ 230 do_action_ref_array( 'http_api_curl', array( &$handle, $parsed_args, $url ) ); 231 232 // We don't need to return the body, so don't. Just execute request and return. 233 if ( ! $parsed_args['blocking'] ) { 234 curl_exec( $handle ); 235 236 $curl_error = curl_error( $handle ); 237 if ( $curl_error ) { 238 curl_close( $handle ); 239 return new WP_Error( 'http_request_failed', $curl_error ); 240 } 241 if ( in_array( curl_getinfo( $handle, CURLINFO_HTTP_CODE ), array( 301, 302 ), true ) ) { 242 curl_close( $handle ); 243 return new WP_Error( 'http_request_failed', __( 'Too many redirects.' ) ); 244 } 245 246 curl_close( $handle ); 247 return array( 248 'headers' => array(), 249 'body' => '', 250 'response' => array( 251 'code' => false, 252 'message' => false, 253 ), 254 'cookies' => array(), 255 ); 256 } 257 258 curl_exec( $handle ); 259 260 $processed_headers = WP_Http::processHeaders( $this->headers, $url ); 261 $body = $this->body; 262 $bytes_written_total = $this->bytes_written_total; 263 264 $this->headers = ''; 265 $this->body = ''; 266 $this->bytes_written_total = 0; 267 268 $curl_error = curl_errno( $handle ); 269 270 // If an error occurred, or, no response. 271 if ( $curl_error || ( 0 === strlen( $body ) && empty( $processed_headers['headers'] ) ) ) { 272 if ( CURLE_WRITE_ERROR /* 23 */ === $curl_error ) { 273 if ( ! $this->max_body_length || $this->max_body_length !== $bytes_written_total ) { 274 if ( $parsed_args['stream'] ) { 275 curl_close( $handle ); 276 fclose( $this->stream_handle ); 277 return new WP_Error( 'http_request_failed', __( 'Failed to write request to temporary file.' ) ); 278 } else { 279 curl_close( $handle ); 280 return new WP_Error( 'http_request_failed', curl_error( $handle ) ); 281 } 282 } 283 } else { 284 $curl_error = curl_error( $handle ); 285 if ( $curl_error ) { 286 curl_close( $handle ); 287 return new WP_Error( 'http_request_failed', $curl_error ); 288 } 289 } 290 if ( in_array( curl_getinfo( $handle, CURLINFO_HTTP_CODE ), array( 301, 302 ), true ) ) { 291 curl_close( $handle ); 292 return new WP_Error( 'http_request_failed', __( 'Too many redirects.' ) ); 293 } 294 } 295 296 curl_close( $handle ); 297 298 if ( $parsed_args['stream'] ) { 299 fclose( $this->stream_handle ); 300 } 301 302 $response = array( 303 'headers' => $processed_headers['headers'], 304 'body' => null, 305 'response' => $processed_headers['response'], 306 'cookies' => $processed_headers['cookies'], 307 'filename' => $parsed_args['filename'], 308 ); 309 310 // Handle redirects. 311 $redirect_response = WP_Http::handle_redirects( $url, $parsed_args, $response ); 312 if ( false !== $redirect_response ) { 313 return $redirect_response; 314 } 315 316 if ( true === $parsed_args['decompress'] 317 && true === WP_Http_Encoding::should_decode( $processed_headers['headers'] ) 318 ) { 319 $body = WP_Http_Encoding::decompress( $body ); 320 } 321 322 $response['body'] = $body; 323 324 return $response; 325 } 326 327 /** 328 * Grabs the headers of the cURL request. 329 * 330 * Each header is sent individually to this callback, so we append to the `$header` property 331 * for temporary storage 332 * 333 * @since 3.2.0 334 * 335 * @param resource $handle cURL handle. 336 * @param string $headers cURL request headers. 337 * @return int Length of the request headers. 338 */ 339 private function stream_headers( $handle, $headers ) { 340 $this->headers .= $headers; 341 return strlen( $headers ); 342 } 343 344 /** 345 * Grabs the body of the cURL request. 346 * 347 * The contents of the document are passed in chunks, so we append to the `$body` 348 * property for temporary storage. Returning a length shorter than the length of 349 * `$data` passed in will cause cURL to abort the request with `CURLE_WRITE_ERROR`. 350 * 351 * @since 3.6.0 352 * 353 * @param resource $handle cURL handle. 354 * @param string $data cURL request body. 355 * @return int Total bytes of data written. 356 */ 357 private function stream_body( $handle, $data ) { 358 $data_length = strlen( $data ); 359 360 if ( $this->max_body_length && ( $this->bytes_written_total + $data_length ) > $this->max_body_length ) { 361 $data_length = ( $this->max_body_length - $this->bytes_written_total ); 362 $data = substr( $data, 0, $data_length ); 363 } 364 365 if ( $this->stream_handle ) { 366 $bytes_written = fwrite( $this->stream_handle, $data ); 367 } else { 368 $this->body .= $data; 369 $bytes_written = $data_length; 370 } 371 372 $this->bytes_written_total += $bytes_written; 373 374 // Upon event of this function returning less than strlen( $data ) curl will error with CURLE_WRITE_ERROR. 375 return $bytes_written; 376 } 377 378 /** 379 * Determines whether this class can be used for retrieving a URL. 380 * 381 * @since 2.7.0 382 * 383 * @param array $args Optional. Array of request arguments. Default empty array. 384 * @return bool False means this class can not be used, true means it can. 385 */ 386 public static function test( $args = array() ) { 387 if ( ! function_exists( 'curl_init' ) || ! function_exists( 'curl_exec' ) ) { 388 return false; 389 } 390 391 $is_ssl = isset( $args['ssl'] ) && $args['ssl']; 392 393 if ( $is_ssl ) { 394 $curl_version = curl_version(); 395 // Check whether this cURL version support SSL requests. 396 if ( ! ( CURL_VERSION_SSL & $curl_version['features'] ) ) { 397 return false; 398 } 399 } 400 401 /** 402 * Filters whether cURL can be used as a transport for retrieving a URL. 403 * 404 * @since 2.7.0 405 * 406 * @param bool $use_class Whether the class can be used. Default true. 407 * @param array $args An array of request arguments. 408 */ 409 return apply_filters( 'use_curl_transport', true, $args ); 410 } 411 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Sat Nov 23 01:00:02 2024 | Cross-referenced by PHPXref 0.7.1 |