[ 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              if ( $curl_error ) {
 244                  curl_close( $handle );
 245                  return new WP_Error( 'http_request_failed', $curl_error );
 246              }
 247              if ( in_array( curl_getinfo( $handle, CURLINFO_HTTP_CODE ), array( 301, 302 ), true ) ) {
 248                  curl_close( $handle );
 249                  return new WP_Error( 'http_request_failed', __( 'Too many redirects.' ) );
 250              }
 251  
 252              curl_close( $handle );
 253              return array(
 254                  'headers'  => array(),
 255                  'body'     => '',
 256                  'response' => array(
 257                      'code'    => false,
 258                      'message' => false,
 259                  ),
 260                  'cookies'  => array(),
 261              );
 262          }
 263  
 264          curl_exec( $handle );
 265  
 266          $processed_headers   = WP_Http::processHeaders( $this->headers, $url );
 267          $body                = $this->body;
 268          $bytes_written_total = $this->bytes_written_total;
 269  
 270          $this->headers             = '';
 271          $this->body                = '';
 272          $this->bytes_written_total = 0;
 273  
 274          $curl_error = curl_errno( $handle );
 275  
 276          // If an error occurred, or, no response.
 277          if ( $curl_error || ( 0 === strlen( $body ) && empty( $processed_headers['headers'] ) ) ) {
 278              if ( CURLE_WRITE_ERROR /* 23 */ === $curl_error ) {
 279                  if ( ! $this->max_body_length || $this->max_body_length !== $bytes_written_total ) {
 280                      if ( $parsed_args['stream'] ) {
 281                          curl_close( $handle );
 282                          fclose( $this->stream_handle );
 283                          return new WP_Error( 'http_request_failed', __( 'Failed to write request to temporary file.' ) );
 284                      } else {
 285                          curl_close( $handle );
 286                          return new WP_Error( 'http_request_failed', curl_error( $handle ) );
 287                      }
 288                  }
 289              } else {
 290                  $curl_error = curl_error( $handle );
 291                  if ( $curl_error ) {
 292                      curl_close( $handle );
 293                      return new WP_Error( 'http_request_failed', $curl_error );
 294                  }
 295              }
 296              if ( in_array( curl_getinfo( $handle, CURLINFO_HTTP_CODE ), array( 301, 302 ), true ) ) {
 297                  curl_close( $handle );
 298                  return new WP_Error( 'http_request_failed', __( 'Too many redirects.' ) );
 299              }
 300          }
 301  
 302          curl_close( $handle );
 303  
 304          if ( $parsed_args['stream'] ) {
 305              fclose( $this->stream_handle );
 306          }
 307  
 308          $response = array(
 309              'headers'  => $processed_headers['headers'],
 310              'body'     => null,
 311              'response' => $processed_headers['response'],
 312              'cookies'  => $processed_headers['cookies'],
 313              'filename' => $parsed_args['filename'],
 314          );
 315  
 316          // Handle redirects.
 317          $redirect_response = WP_Http::handle_redirects( $url, $parsed_args, $response );
 318          if ( false !== $redirect_response ) {
 319              return $redirect_response;
 320          }
 321  
 322          if ( true === $parsed_args['decompress']
 323              && true === WP_Http_Encoding::should_decode( $processed_headers['headers'] )
 324          ) {
 325              $body = WP_Http_Encoding::decompress( $body );
 326          }
 327  
 328          $response['body'] = $body;
 329  
 330          return $response;
 331      }
 332  
 333      /**
 334       * Grabs the headers of the cURL request.
 335       *
 336       * Each header is sent individually to this callback, and is appended to the `$header` property
 337       * for temporary storage.
 338       *
 339       * @since 3.2.0
 340       *
 341       * @param resource $handle  cURL handle.
 342       * @param string   $headers cURL request headers.
 343       * @return int Length of the request headers.
 344       */
 345  	private function stream_headers( $handle, $headers ) {
 346          $this->headers .= $headers;
 347          return strlen( $headers );
 348      }
 349  
 350      /**
 351       * Grabs the body of the cURL request.
 352       *
 353       * The contents of the document are passed in chunks, and are appended to the `$body`
 354       * property for temporary storage. Returning a length shorter than the length of
 355       * `$data` passed in will cause cURL to abort the request with `CURLE_WRITE_ERROR`.
 356       *
 357       * @since 3.6.0
 358       *
 359       * @param resource $handle cURL handle.
 360       * @param string   $data   cURL request body.
 361       * @return int Total bytes of data written.
 362       */
 363  	private function stream_body( $handle, $data ) {
 364          $data_length = strlen( $data );
 365  
 366          if ( $this->max_body_length && ( $this->bytes_written_total + $data_length ) > $this->max_body_length ) {
 367              $data_length = ( $this->max_body_length - $this->bytes_written_total );
 368              $data        = substr( $data, 0, $data_length );
 369          }
 370  
 371          if ( $this->stream_handle ) {
 372              $bytes_written = fwrite( $this->stream_handle, $data );
 373          } else {
 374              $this->body   .= $data;
 375              $bytes_written = $data_length;
 376          }
 377  
 378          $this->bytes_written_total += $bytes_written;
 379  
 380          // Upon event of this function returning less than strlen( $data ) curl will error with CURLE_WRITE_ERROR.
 381          return $bytes_written;
 382      }
 383  
 384      /**
 385       * Determines whether this class can be used for retrieving a URL.
 386       *
 387       * @since 2.7.0
 388       *
 389       * @param array $args Optional. Array of request arguments. Default empty array.
 390       * @return bool False means this class can not be used, true means it can.
 391       */
 392  	public static function test( $args = array() ) {
 393          if ( ! function_exists( 'curl_init' ) || ! function_exists( 'curl_exec' ) ) {
 394              return false;
 395          }
 396  
 397          $is_ssl = isset( $args['ssl'] ) && $args['ssl'];
 398  
 399          if ( $is_ssl ) {
 400              $curl_version = curl_version();
 401              // Check whether this cURL version support SSL requests.
 402              if ( ! ( CURL_VERSION_SSL & $curl_version['features'] ) ) {
 403                  return false;
 404              }
 405          }
 406  
 407          /**
 408           * Filters whether cURL can be used as a transport for retrieving a URL.
 409           *
 410           * @since 2.7.0
 411           *
 412           * @param bool  $use_class Whether the class can be used. Default true.
 413           * @param array $args      An array of request arguments.
 414           */
 415          return apply_filters( 'use_curl_transport', true, $args );
 416      }
 417  }


Generated : Fri Apr 26 08:20:02 2024 Cross-referenced by PHPXref