[ Index ]

PHP Cross Reference of WordPress

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   */
  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  }


Generated: Sat Nov 23 01:00:02 2024 Cross-referenced by PHPXref 0.7.1