[ Index ]

PHP Cross Reference of WordPress Trunk (Updated Daily)

Search

title

Body

[close]

/wp-includes/ -> class-wp-http-curl.php (source)

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


Generated : Thu Sep 18 08:20:05 2025 Cross-referenced by PHPXref