[ Index ]

PHP Cross Reference of WordPress Trunk (Updated Daily)

title

Body

[close]

/wp-includes/Requests/Transport/ -> cURL.php (source)

   1  <?php
   2  /**
   3   * cURL HTTP transport
   4   *
   5   * @package Requests
   6   * @subpackage Transport
   7   */
   8  
   9  /**
  10   * cURL HTTP transport
  11   *
  12   * @package Requests
  13   * @subpackage Transport
  14   */
  15  class Requests_Transport_cURL implements Requests_Transport {
  16      const CURL_7_10_5 = 0x070A05;
  17      const CURL_7_16_2 = 0x071002;
  18  
  19      /**
  20       * Raw HTTP data
  21       *
  22       * @var string
  23       */
  24      public $headers = '';
  25  
  26      /**
  27       * Raw body data
  28       *
  29       * @var string
  30       */
  31      public $response_data = '';
  32  
  33      /**
  34       * Information on the current request
  35       *
  36       * @var array cURL information array, see {@see https://secure.php.net/curl_getinfo}
  37       */
  38      public $info;
  39  
  40      /**
  41       * Version string
  42       *
  43       * @var long
  44       */
  45      public $version;
  46  
  47      /**
  48       * cURL handle
  49       *
  50       * @var resource
  51       */
  52      protected $handle;
  53  
  54      /**
  55       * Hook dispatcher instance
  56       *
  57       * @var Requests_Hooks
  58       */
  59      protected $hooks;
  60  
  61      /**
  62       * Have we finished the headers yet?
  63       *
  64       * @var boolean
  65       */
  66      protected $done_headers = false;
  67  
  68      /**
  69       * If streaming to a file, keep the file pointer
  70       *
  71       * @var resource
  72       */
  73      protected $stream_handle;
  74  
  75      /**
  76       * How many bytes are in the response body?
  77       *
  78       * @var int
  79       */
  80      protected $response_bytes;
  81  
  82      /**
  83       * What's the maximum number of bytes we should keep?
  84       *
  85       * @var int|bool Byte count, or false if no limit.
  86       */
  87      protected $response_byte_limit;
  88  
  89      /**
  90       * Constructor
  91       */
  92  	public function __construct() {
  93          $curl = curl_version();
  94          $this->version = $curl['version_number'];
  95          $this->handle = curl_init();
  96  
  97          curl_setopt($this->handle, CURLOPT_HEADER, false);
  98          curl_setopt($this->handle, CURLOPT_RETURNTRANSFER, 1);
  99          if ($this->version >= self::CURL_7_10_5) {
 100              curl_setopt($this->handle, CURLOPT_ENCODING, '');
 101          }
 102          if (defined('CURLOPT_PROTOCOLS')) {
 103              curl_setopt($this->handle, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);
 104          }
 105          if (defined('CURLOPT_REDIR_PROTOCOLS')) {
 106              curl_setopt($this->handle, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);
 107          }
 108      }
 109  
 110      /**
 111       * Destructor
 112       */
 113  	public function __destruct() {
 114          if (is_resource($this->handle)) {
 115              curl_close($this->handle);
 116          }
 117      }
 118  
 119      /**
 120       * Perform a request
 121       *
 122       * @throws Requests_Exception On a cURL error (`curlerror`)
 123       *
 124       * @param string $url URL to request
 125       * @param array $headers Associative array of request headers
 126       * @param string|array $data Data to send either as the POST body, or as parameters in the URL for a GET/HEAD
 127       * @param array $options Request options, see {@see Requests::response()} for documentation
 128       * @return string Raw HTTP result
 129       */
 130  	public function request($url, $headers = array(), $data = array(), $options = array()) {
 131          $this->hooks = $options['hooks'];
 132  
 133          $this->setup_handle($url, $headers, $data, $options);
 134  
 135          $options['hooks']->dispatch('curl.before_send', array(&$this->handle));
 136  
 137          if ($options['filename'] !== false) {
 138              $this->stream_handle = fopen($options['filename'], 'wb');
 139          }
 140  
 141          $this->response_data = '';
 142          $this->response_bytes = 0;
 143          $this->response_byte_limit = false;
 144          if ($options['max_bytes'] !== false) {
 145              $this->response_byte_limit = $options['max_bytes'];
 146          }
 147  
 148          if (isset($options['verify'])) {
 149              if ($options['verify'] === false) {
 150                  curl_setopt($this->handle, CURLOPT_SSL_VERIFYHOST, 0);
 151                  curl_setopt($this->handle, CURLOPT_SSL_VERIFYPEER, 0);
 152              }
 153              elseif (is_string($options['verify'])) {
 154                  curl_setopt($this->handle, CURLOPT_CAINFO, $options['verify']);
 155              }
 156          }
 157  
 158          if (isset($options['verifyname']) && $options['verifyname'] === false) {
 159              curl_setopt($this->handle, CURLOPT_SSL_VERIFYHOST, 0);
 160          }
 161  
 162          curl_exec($this->handle);
 163          $response = $this->response_data;
 164  
 165          $options['hooks']->dispatch('curl.after_send', array());
 166  
 167          if (curl_errno($this->handle) === 23 || curl_errno($this->handle) === 61) {
 168              // Reset encoding and try again
 169              curl_setopt($this->handle, CURLOPT_ENCODING, 'none');
 170  
 171              $this->response_data = '';
 172              $this->response_bytes = 0;
 173              curl_exec($this->handle);
 174              $response = $this->response_data;
 175          }
 176  
 177          $this->process_response($response, $options);
 178  
 179          // Need to remove the $this reference from the curl handle.
 180          // Otherwise Requests_Transport_cURL wont be garbage collected and the curl_close() will never be called.
 181          curl_setopt($this->handle, CURLOPT_HEADERFUNCTION, null);
 182          curl_setopt($this->handle, CURLOPT_WRITEFUNCTION, null);
 183  
 184          return $this->headers;
 185      }
 186  
 187      /**
 188       * Send multiple requests simultaneously
 189       *
 190       * @param array $requests Request data
 191       * @param array $options Global options
 192       * @return array Array of Requests_Response objects (may contain Requests_Exception or string responses as well)
 193       */
 194  	public function request_multiple($requests, $options) {
 195          // If you're not requesting, we can't get any responses ¯\_(ツ)_/¯
 196          if (empty($requests)) {
 197              return array();
 198          }
 199  
 200          $multihandle = curl_multi_init();
 201          $subrequests = array();
 202          $subhandles = array();
 203  
 204          $class = get_class($this);
 205          foreach ($requests as $id => $request) {
 206              $subrequests[$id] = new $class();
 207              $subhandles[$id] = $subrequests[$id]->get_subrequest_handle($request['url'], $request['headers'], $request['data'], $request['options']);
 208              $request['options']['hooks']->dispatch('curl.before_multi_add', array(&$subhandles[$id]));
 209              curl_multi_add_handle($multihandle, $subhandles[$id]);
 210          }
 211  
 212          $completed = 0;
 213          $responses = array();
 214  
 215          $request['options']['hooks']->dispatch('curl.before_multi_exec', array(&$multihandle));
 216  
 217          do {
 218              $active = false;
 219  
 220              do {
 221                  $status = curl_multi_exec($multihandle, $active);
 222              }
 223              while ($status === CURLM_CALL_MULTI_PERFORM);
 224  
 225              $to_process = array();
 226  
 227              // Read the information as needed
 228              while ($done = curl_multi_info_read($multihandle)) {
 229                  $key = array_search($done['handle'], $subhandles, true);
 230                  if (!isset($to_process[$key])) {
 231                      $to_process[$key] = $done;
 232                  }
 233              }
 234  
 235              // Parse the finished requests before we start getting the new ones
 236              foreach ($to_process as $key => $done) {
 237                  $options = $requests[$key]['options'];
 238                  if (CURLE_OK !== $done['result']) {
 239                      //get error string for handle.
 240                      $reason = curl_error($done['handle']);
 241                      $exception = new Requests_Exception_Transport_cURL(
 242                                      $reason,
 243                                      Requests_Exception_Transport_cURL::EASY,
 244                                      $done['handle'],
 245                                      $done['result']
 246                                  );
 247                      $responses[$key] = $exception;
 248                      $options['hooks']->dispatch('transport.internal.parse_error', array(&$responses[$key], $requests[$key]));
 249                  }
 250                  else {
 251                      $responses[$key] = $subrequests[$key]->process_response($subrequests[$key]->response_data, $options);
 252  
 253                      $options['hooks']->dispatch('transport.internal.parse_response', array(&$responses[$key], $requests[$key]));
 254                  }
 255  
 256                  curl_multi_remove_handle($multihandle, $done['handle']);
 257                  curl_close($done['handle']);
 258  
 259                  if (!is_string($responses[$key])) {
 260                      $options['hooks']->dispatch('multiple.request.complete', array(&$responses[$key], $key));
 261                  }
 262                  $completed++;
 263              }
 264          }
 265          while ($active || $completed < count($subrequests));
 266  
 267          $request['options']['hooks']->dispatch('curl.after_multi_exec', array(&$multihandle));
 268  
 269          curl_multi_close($multihandle);
 270  
 271          return $responses;
 272      }
 273  
 274      /**
 275       * Get the cURL handle for use in a multi-request
 276       *
 277       * @param string $url URL to request
 278       * @param array $headers Associative array of request headers
 279       * @param string|array $data Data to send either as the POST body, or as parameters in the URL for a GET/HEAD
 280       * @param array $options Request options, see {@see Requests::response()} for documentation
 281       * @return resource Subrequest's cURL handle
 282       */
 283      public function &get_subrequest_handle($url, $headers, $data, $options) {
 284          $this->setup_handle($url, $headers, $data, $options);
 285  
 286          if ($options['filename'] !== false) {
 287              $this->stream_handle = fopen($options['filename'], 'wb');
 288          }
 289  
 290          $this->response_data = '';
 291          $this->response_bytes = 0;
 292          $this->response_byte_limit = false;
 293          if ($options['max_bytes'] !== false) {
 294              $this->response_byte_limit = $options['max_bytes'];
 295          }
 296          $this->hooks = $options['hooks'];
 297  
 298          return $this->handle;
 299      }
 300  
 301      /**
 302       * Setup the cURL handle for the given data
 303       *
 304       * @param string $url URL to request
 305       * @param array $headers Associative array of request headers
 306       * @param string|array $data Data to send either as the POST body, or as parameters in the URL for a GET/HEAD
 307       * @param array $options Request options, see {@see Requests::response()} for documentation
 308       */
 309  	protected function setup_handle($url, $headers, $data, $options) {
 310          $options['hooks']->dispatch('curl.before_request', array(&$this->handle));
 311  
 312          // Force closing the connection for old versions of cURL (<7.22).
 313          if ( ! isset( $headers['Connection'] ) ) {
 314              $headers['Connection'] = 'close';
 315          }
 316  
 317          $headers = Requests::flatten($headers);
 318  
 319          if (!empty($data)) {
 320              $data_format = $options['data_format'];
 321  
 322              if ($data_format === 'query') {
 323                  $url = self::format_get($url, $data);
 324                  $data = '';
 325              }
 326              elseif (!is_string($data)) {
 327                  $data = http_build_query($data, null, '&');
 328              }
 329          }
 330  
 331          switch ($options['type']) {
 332              case Requests::POST:
 333                  curl_setopt($this->handle, CURLOPT_POST, true);
 334                  curl_setopt($this->handle, CURLOPT_POSTFIELDS, $data);
 335                  break;
 336              case Requests::HEAD:
 337                  curl_setopt($this->handle, CURLOPT_CUSTOMREQUEST, $options['type']);
 338                  curl_setopt($this->handle, CURLOPT_NOBODY, true);
 339                  break;
 340              case Requests::TRACE:
 341                  curl_setopt($this->handle, CURLOPT_CUSTOMREQUEST, $options['type']);
 342                  break;
 343              case Requests::PATCH:
 344              case Requests::PUT:
 345              case Requests::DELETE:
 346              case Requests::OPTIONS:
 347              default:
 348                  curl_setopt($this->handle, CURLOPT_CUSTOMREQUEST, $options['type']);
 349                  if (!empty($data)) {
 350                      curl_setopt($this->handle, CURLOPT_POSTFIELDS, $data);
 351                  }
 352          }
 353  
 354          // cURL requires a minimum timeout of 1 second when using the system
 355          // DNS resolver, as it uses `alarm()`, which is second resolution only.
 356          // There's no way to detect which DNS resolver is being used from our
 357          // end, so we need to round up regardless of the supplied timeout.
 358          //
 359          // https://github.com/curl/curl/blob/4f45240bc84a9aa648c8f7243be7b79e9f9323a5/lib/hostip.c#L606-L609
 360          $timeout = max($options['timeout'], 1);
 361  
 362          if (is_int($timeout) || $this->version < self::CURL_7_16_2) {
 363              curl_setopt($this->handle, CURLOPT_TIMEOUT, ceil($timeout));
 364          }
 365          else {
 366              curl_setopt($this->handle, CURLOPT_TIMEOUT_MS, round($timeout * 1000));
 367          }
 368  
 369          if (is_int($options['connect_timeout']) || $this->version < self::CURL_7_16_2) {
 370              curl_setopt($this->handle, CURLOPT_CONNECTTIMEOUT, ceil($options['connect_timeout']));
 371          }
 372          else {
 373              curl_setopt($this->handle, CURLOPT_CONNECTTIMEOUT_MS, round($options['connect_timeout'] * 1000));
 374          }
 375          curl_setopt($this->handle, CURLOPT_URL, $url);
 376          curl_setopt($this->handle, CURLOPT_REFERER, $url);
 377          curl_setopt($this->handle, CURLOPT_USERAGENT, $options['useragent']);
 378          if (!empty($headers)) {
 379              curl_setopt($this->handle, CURLOPT_HTTPHEADER, $headers);
 380          }
 381          if ($options['protocol_version'] === 1.1) {
 382              curl_setopt($this->handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
 383          }
 384          else {
 385              curl_setopt($this->handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
 386          }
 387  
 388          if (true === $options['blocking']) {
 389              curl_setopt($this->handle, CURLOPT_HEADERFUNCTION, array(&$this, 'stream_headers'));
 390              curl_setopt($this->handle, CURLOPT_WRITEFUNCTION, array(&$this, 'stream_body'));
 391              curl_setopt($this->handle, CURLOPT_BUFFERSIZE, Requests::BUFFER_SIZE);
 392          }
 393      }
 394  
 395      /**
 396       * Process a response
 397       *
 398       * @param string $response Response data from the body
 399       * @param array $options Request options
 400       * @return string HTTP response data including headers
 401       */
 402  	public function process_response($response, $options) {
 403          if ($options['blocking'] === false) {
 404              $fake_headers = '';
 405              $options['hooks']->dispatch('curl.after_request', array(&$fake_headers));
 406              return false;
 407          }
 408          if ($options['filename'] !== false) {
 409              fclose($this->stream_handle);
 410              $this->headers = trim($this->headers);
 411          }
 412          else {
 413              $this->headers .= $response;
 414          }
 415  
 416          if (curl_errno($this->handle)) {
 417              $error = sprintf(
 418                  'cURL error %s: %s',
 419                  curl_errno($this->handle),
 420                  curl_error($this->handle)
 421              );
 422              throw new Requests_Exception($error, 'curlerror', $this->handle);
 423          }
 424          $this->info = curl_getinfo($this->handle);
 425  
 426          $options['hooks']->dispatch('curl.after_request', array(&$this->headers, &$this->info));
 427          return $this->headers;
 428      }
 429  
 430      /**
 431       * Collect the headers as they are received
 432       *
 433       * @param resource $handle cURL resource
 434       * @param string $headers Header string
 435       * @return integer Length of provided header
 436       */
 437  	public function stream_headers($handle, $headers) {
 438          // Why do we do this? cURL will send both the final response and any
 439          // interim responses, such as a 100 Continue. We don't need that.
 440          // (We may want to keep this somewhere just in case)
 441          if ($this->done_headers) {
 442              $this->headers = '';
 443              $this->done_headers = false;
 444          }
 445          $this->headers .= $headers;
 446  
 447          if ($headers === "\r\n") {
 448              $this->done_headers = true;
 449          }
 450          return strlen($headers);
 451      }
 452  
 453      /**
 454       * Collect data as it's received
 455       *
 456       * @since 1.6.1
 457       *
 458       * @param resource $handle cURL resource
 459       * @param string $data Body data
 460       * @return integer Length of provided data
 461       */
 462  	public function stream_body($handle, $data) {
 463          $this->hooks->dispatch('request.progress', array($data, $this->response_bytes, $this->response_byte_limit));
 464          $data_length = strlen($data);
 465  
 466          // Are we limiting the response size?
 467          if ($this->response_byte_limit) {
 468              if ($this->response_bytes === $this->response_byte_limit) {
 469                  // Already at maximum, move on
 470                  return $data_length;
 471              }
 472  
 473              if (($this->response_bytes + $data_length) > $this->response_byte_limit) {
 474                  // Limit the length
 475                  $limited_length = ($this->response_byte_limit - $this->response_bytes);
 476                  $data = substr($data, 0, $limited_length);
 477              }
 478          }
 479  
 480          if ($this->stream_handle) {
 481              fwrite($this->stream_handle, $data);
 482          }
 483          else {
 484              $this->response_data .= $data;
 485          }
 486  
 487          $this->response_bytes += strlen($data);
 488          return $data_length;
 489      }
 490  
 491      /**
 492       * Format a URL given GET data
 493       *
 494       * @param string $url
 495       * @param array|object $data Data to build query using, see {@see https://secure.php.net/http_build_query}
 496       * @return string URL with data
 497       */
 498  	protected static function format_get($url, $data) {
 499          if (!empty($data)) {
 500              $url_parts = parse_url($url);
 501              if (empty($url_parts['query'])) {
 502                  $query = $url_parts['query'] = '';
 503              }
 504              else {
 505                  $query = $url_parts['query'];
 506              }
 507  
 508              $query .= '&' . http_build_query($data, null, '&');
 509              $query = trim($query, '&');
 510  
 511              if (empty($url_parts['query'])) {
 512                  $url .= '?' . $query;
 513              }
 514              else {
 515                  $url = str_replace($url_parts['query'], $query, $url);
 516              }
 517          }
 518          return $url;
 519      }
 520  
 521      /**
 522       * Whether this transport is valid
 523       *
 524       * @codeCoverageIgnore
 525       * @return boolean True if the transport is valid, false otherwise.
 526       */
 527  	public static function test($capabilities = array()) {
 528          if (!function_exists('curl_init') || !function_exists('curl_exec')) {
 529              return false;
 530          }
 531  
 532          // If needed, check that our installed curl version supports SSL
 533          if (isset($capabilities['ssl']) && $capabilities['ssl']) {
 534              $curl_version = curl_version();
 535              if (!(CURL_VERSION_SSL & $curl_version['features'])) {
 536                  return false;
 537              }
 538          }
 539  
 540          return true;
 541      }
 542  }


Generated: Wed Oct 23 08:20:01 2019 Cross-referenced by PHPXref 0.7