Ampache PHP Cross Reference Groupware Applications

Source: /modules/Guzzle/Http/Curl/CurlHandle.php - 464 lines - 15722 bytes - Summary - Text - Print

   1  <?php
   2  
   3  namespace Guzzle\Http\Curl;
   4  
   5  use Guzzle\Common\Exception\InvalidArgumentException;
   6  use Guzzle\Common\Exception\RuntimeException;
   7  use Guzzle\Common\Collection;
   8  use Guzzle\Http\Message\EntityEnclosingRequest;
   9  use Guzzle\Http\Message\RequestInterface;
  10  use Guzzle\Parser\ParserRegistry;
  11  use Guzzle\Http\Url;
  12  
  13  /**
  14   * Immutable wrapper for a cURL handle
  15   */
  16  class CurlHandle
  17  {
  18      const BODY_AS_STRING = 'body_as_string';
  19      const PROGRESS = 'progress';
  20      const DEBUG = 'debug';
  21  
  22      /** @var Collection Curl options */
  23      protected $options;
  24  
  25      /** @var resource Curl resource handle */
  26      protected $handle;
  27  
  28      /** @var int CURLE_* error */
  29      protected $errorNo = CURLE_OK;
  30  
  31      /**
  32       * Factory method to create a new curl handle based on an HTTP request.
  33       *
  34       * There are some helpful options you can set to enable specific behavior:
  35       * - debug:    Set to true to enable cURL debug functionality to track the actual headers sent over the wire.
  36       * - progress: Set to true to enable progress function callbacks.
  37       *
  38       * @param RequestInterface $request Request
  39       *
  40       * @return CurlHandle
  41       * @throws RuntimeException
  42       */
  43      public static function factory(RequestInterface $request)
  44      {
  45          $requestCurlOptions = $request->getCurlOptions();
  46          $mediator = new RequestMediator($request, $requestCurlOptions->get('emit_io'));
  47          $tempContentLength = null;
  48          $method = $request->getMethod();
  49          $bodyAsString = $requestCurlOptions->get(self::BODY_AS_STRING);
  50  
  51          // Prepare url
  52          $url = (string)$request->getUrl();
  53          if(($pos = strpos($url, '#')) !== false ){
  54              // strip fragment from url
  55              $url = substr($url, 0, $pos);
  56          }
  57  
  58          // Array of default cURL options.
  59          $curlOptions = array(
  60              CURLOPT_URL            => $url,
  61              CURLOPT_CONNECTTIMEOUT => 150,
  62              CURLOPT_RETURNTRANSFER => false,
  63              CURLOPT_HEADER         => false,
  64              CURLOPT_PORT           => $request->getPort(),
  65              CURLOPT_HTTPHEADER     => array(),
  66              CURLOPT_WRITEFUNCTION  => array($mediator, 'writeResponseBody'),
  67              CURLOPT_HEADERFUNCTION => array($mediator, 'receiveResponseHeader'),
  68              CURLOPT_HTTP_VERSION   => $request->getProtocolVersion() === '1.0'
  69                  ? CURL_HTTP_VERSION_1_0 : CURL_HTTP_VERSION_1_1,
  70              // Verifies the authenticity of the peer's certificate
  71              CURLOPT_SSL_VERIFYPEER => 1,
  72              // Certificate must indicate that the server is the server to which you meant to connect
  73              CURLOPT_SSL_VERIFYHOST => 2
  74          );
  75  
  76          if (defined('CURLOPT_PROTOCOLS')) {
  77              // Allow only HTTP and HTTPS protocols
  78              $curlOptions[CURLOPT_PROTOCOLS] = CURLPROTO_HTTP | CURLPROTO_HTTPS;
  79          }
  80  
  81          // Add CURLOPT_ENCODING if Accept-Encoding header is provided
  82          if ($acceptEncodingHeader = $request->getHeader('Accept-Encoding')) {
  83              $curlOptions[CURLOPT_ENCODING] = (string) $acceptEncodingHeader;
  84              // Let cURL set the Accept-Encoding header, prevents duplicate values
  85              $request->removeHeader('Accept-Encoding');
  86          }
  87  
  88          // Enable curl debug information if the 'debug' param was set
  89          if ($requestCurlOptions->get('debug')) {
  90              $curlOptions[CURLOPT_STDERR] = fopen('php://temp', 'r+');
  91              // @codeCoverageIgnoreStart
  92              if (false === $curlOptions[CURLOPT_STDERR]) {
  93                  throw new RuntimeException('Unable to create a stream for CURLOPT_STDERR');
  94              }
  95              // @codeCoverageIgnoreEnd
  96              $curlOptions[CURLOPT_VERBOSE] = true;
  97          }
  98  
  99          // Specify settings according to the HTTP method
 100          if ($method == 'GET') {
 101              $curlOptions[CURLOPT_HTTPGET] = true;
 102          } elseif ($method == 'HEAD') {
 103              $curlOptions[CURLOPT_NOBODY] = true;
 104              // HEAD requests do not use a write function
 105              unset($curlOptions[CURLOPT_WRITEFUNCTION]);
 106          } elseif (!($request instanceof EntityEnclosingRequest)) {
 107              $curlOptions[CURLOPT_CUSTOMREQUEST] = $method;
 108          } else {
 109  
 110              $curlOptions[CURLOPT_CUSTOMREQUEST] = $method;
 111  
 112              // Handle sending raw bodies in a request
 113              if ($request->getBody()) {
 114                  // You can send the body as a string using curl's CURLOPT_POSTFIELDS
 115                  if ($bodyAsString) {
 116                      $curlOptions[CURLOPT_POSTFIELDS] = (string) $request->getBody();
 117                      // Allow curl to add the Content-Length for us to account for the times when
 118                      // POST redirects are followed by GET requests
 119                      if ($tempContentLength = $request->getHeader('Content-Length')) {
 120                          $tempContentLength = (int) (string) $tempContentLength;
 121                      }
 122                      // Remove the curl generated Content-Type header if none was set manually
 123                      if (!$request->hasHeader('Content-Type')) {
 124                          $curlOptions[CURLOPT_HTTPHEADER][] = 'Content-Type:';
 125                      }
 126                  } else {
 127                      $curlOptions[CURLOPT_UPLOAD] = true;
 128                      // Let cURL handle setting the Content-Length header
 129                      if ($tempContentLength = $request->getHeader('Content-Length')) {
 130                          $tempContentLength = (int) (string) $tempContentLength;
 131                          $curlOptions[CURLOPT_INFILESIZE] = $tempContentLength;
 132                      }
 133                      // Add a callback for curl to read data to send with the request only if a body was specified
 134                      $curlOptions[CURLOPT_READFUNCTION] = array($mediator, 'readRequestBody');
 135                      // Attempt to seek to the start of the stream
 136                      $request->getBody()->seek(0);
 137                  }
 138  
 139              } else {
 140  
 141                  // Special handling for POST specific fields and files
 142                  $postFields = false;
 143                  if (count($request->getPostFiles())) {
 144                      $postFields = $request->getPostFields()->useUrlEncoding(false)->urlEncode();
 145                      foreach ($request->getPostFiles() as $key => $data) {
 146                          $prefixKeys = count($data) > 1;
 147                          foreach ($data as $index => $file) {
 148                              // Allow multiple files in the same key
 149                              $fieldKey = $prefixKeys ? "{$key}[{$index}]" : $key;
 150                              $postFields[$fieldKey] = $file->getCurlValue();
 151                          }
 152                      }
 153                  } elseif (count($request->getPostFields())) {
 154                      $postFields = (string) $request->getPostFields()->useUrlEncoding(true);
 155                  }
 156  
 157                  if ($postFields !== false) {
 158                      if ($method == 'POST') {
 159                          unset($curlOptions[CURLOPT_CUSTOMREQUEST]);
 160                          $curlOptions[CURLOPT_POST] = true;
 161                      }
 162                      $curlOptions[CURLOPT_POSTFIELDS] = $postFields;
 163                      $request->removeHeader('Content-Length');
 164                  }
 165              }
 166  
 167              // If the Expect header is not present, prevent curl from adding it
 168              if (!$request->hasHeader('Expect')) {
 169                  $curlOptions[CURLOPT_HTTPHEADER][] = 'Expect:';
 170              }
 171          }
 172  
 173          // If a Content-Length header was specified but we want to allow curl to set one for us
 174          if (null !== $tempContentLength) {
 175              $request->removeHeader('Content-Length');
 176          }
 177  
 178          // Set custom cURL options
 179          foreach ($requestCurlOptions->toArray() as $key => $value) {
 180              if (is_numeric($key)) {
 181                  $curlOptions[$key] = $value;
 182              }
 183          }
 184  
 185          // Do not set an Accept header by default
 186          if (!isset($curlOptions[CURLOPT_ENCODING])) {
 187              $curlOptions[CURLOPT_HTTPHEADER][] = 'Accept:';
 188          }
 189  
 190          // Add any custom headers to the request. Empty headers will cause curl to not send the header at all.
 191          foreach ($request->getHeaderLines() as $line) {
 192              $curlOptions[CURLOPT_HTTPHEADER][] = $line;
 193          }
 194  
 195          // Add the content-length header back if it was temporarily removed
 196          if ($tempContentLength) {
 197              $request->setHeader('Content-Length', $tempContentLength);
 198          }
 199  
 200          // Apply the options to a new cURL handle.
 201          $handle = curl_init();
 202  
 203          // Enable the progress function if the 'progress' param was set
 204          if ($requestCurlOptions->get('progress')) {
 205              // Wrap the function in a function that provides the curl handle to the mediator's progress function
 206              // Using this rather than injecting the handle into the mediator prevents a circular reference
 207              $curlOptions[CURLOPT_PROGRESSFUNCTION] = function () use ($mediator, $handle) {
 208                  $args = func_get_args();
 209                  $args[] = $handle;
 210  
 211                  // PHP 5.5 pushed the handle onto the start of the args
 212                  if (is_resource($args[0])) {
 213                      array_shift($args);
 214                  }
 215  
 216                  call_user_func_array(array($mediator, 'progress'), $args);
 217              };
 218              $curlOptions[CURLOPT_NOPROGRESS] = false;
 219          }
 220  
 221          curl_setopt_array($handle, $curlOptions);
 222  
 223          return new static($handle, $curlOptions);
 224      }
 225  
 226      /**
 227       * Construct a new CurlHandle object that wraps a cURL handle
 228       *
 229       * @param resource         $handle  Configured cURL handle resource
 230       * @param Collection|array $options Curl options to use with the handle
 231       *
 232       * @throws InvalidArgumentException
 233       */
 234      public function __construct($handle, $options)
 235      {
 236          if (!is_resource($handle)) {
 237              throw new InvalidArgumentException('Invalid handle provided');
 238          }
 239          if (is_array($options)) {
 240              $this->options = new Collection($options);
 241          } elseif ($options instanceof Collection) {
 242              $this->options = $options;
 243          } else {
 244              throw new InvalidArgumentException('Expected array or Collection');
 245          }
 246          $this->handle = $handle;
 247      }
 248  
 249      /**
 250       * Destructor
 251       */
 252      public function __destruct()
 253      {
 254          $this->close();
 255      }
 256  
 257      /**
 258       * Close the curl handle
 259       */
 260      public function close()
 261      {
 262          if (is_resource($this->handle)) {
 263              curl_close($this->handle);
 264          }
 265          $this->handle = null;
 266      }
 267  
 268      /**
 269       * Check if the handle is available and still OK
 270       *
 271       * @return bool
 272       */
 273      public function isAvailable()
 274      {
 275          return is_resource($this->handle);
 276      }
 277  
 278      /**
 279       * Get the last error that occurred on the cURL handle
 280       *
 281       * @return string
 282       */
 283      public function getError()
 284      {
 285          return $this->isAvailable() ? curl_error($this->handle) : '';
 286      }
 287  
 288      /**
 289       * Get the last error number that occurred on the cURL handle
 290       *
 291       * @return int
 292       */
 293      public function getErrorNo()
 294      {
 295          if ($this->errorNo) {
 296              return $this->errorNo;
 297          }
 298  
 299          return $this->isAvailable() ? curl_errno($this->handle) : CURLE_OK;
 300      }
 301  
 302      /**
 303       * Set the curl error number
 304       *
 305       * @param int $error Error number to set
 306       *
 307       * @return CurlHandle
 308       */
 309      public function setErrorNo($error)
 310      {
 311          $this->errorNo = $error;
 312  
 313          return $this;
 314      }
 315  
 316      /**
 317       * Get cURL curl_getinfo data
 318       *
 319       * @param int $option Option to retrieve. Pass null to retrieve all data as an array.
 320       *
 321       * @return array|mixed
 322       */
 323      public function getInfo($option = null)
 324      {
 325          if (!is_resource($this->handle)) {
 326              return null;
 327          }
 328  
 329          if (null !== $option) {
 330              return curl_getinfo($this->handle, $option) ?: null;
 331          }
 332  
 333          return curl_getinfo($this->handle) ?: array();
 334      }
 335  
 336      /**
 337       * Get the stderr output
 338       *
 339       * @param bool $asResource Set to TRUE to get an fopen resource
 340       *
 341       * @return string|resource|null
 342       */
 343      public function getStderr($asResource = false)
 344      {
 345          $stderr = $this->getOptions()->get(CURLOPT_STDERR);
 346          if (!$stderr) {
 347              return null;
 348          }
 349  
 350          if ($asResource) {
 351              return $stderr;
 352          }
 353  
 354          fseek($stderr, 0);
 355          $e = stream_get_contents($stderr);
 356          fseek($stderr, 0, SEEK_END);
 357  
 358          return $e;
 359      }
 360  
 361      /**
 362       * Get the URL that this handle is connecting to
 363       *
 364       * @return Url
 365       */
 366      public function getUrl()
 367      {
 368          return Url::factory($this->options->get(CURLOPT_URL));
 369      }
 370  
 371      /**
 372       * Get the wrapped curl handle
 373       *
 374       * @return resource|null Returns the cURL handle or null if it was closed
 375       */
 376      public function getHandle()
 377      {
 378          return $this->isAvailable() ? $this->handle : null;
 379      }
 380  
 381      /**
 382       * Get the cURL setopt options of the handle. Changing values in the return object will have no effect on the curl
 383       * handle after it is created.
 384       *
 385       * @return Collection
 386       */
 387      public function getOptions()
 388      {
 389          return $this->options;
 390      }
 391  
 392      /**
 393       * Update a request based on the log messages of the CurlHandle
 394       *
 395       * @param RequestInterface $request Request to update
 396       */
 397      public function updateRequestFromTransfer(RequestInterface $request)
 398      {
 399          if (!$request->getResponse()) {
 400              return;
 401          }
 402  
 403          // Update the transfer stats of the response
 404          $request->getResponse()->setInfo($this->getInfo());
 405  
 406          if (!$log = $this->getStderr(true)) {
 407              return;
 408          }
 409  
 410          // Parse the cURL stderr output for outgoing requests
 411          $headers = '';
 412          fseek($log, 0);
 413          while (($line = fgets($log)) !== false) {
 414              if ($line && $line[0] == '>') {
 415                  $headers = substr(trim($line), 2) . "\r\n";
 416                  while (($line = fgets($log)) !== false) {
 417                      if ($line[0] == '*' || $line[0] == '<') {
 418                          break;
 419                      } else {
 420                          $headers .= trim($line) . "\r\n";
 421                      }
 422                  }
 423              }
 424          }
 425  
 426          // Add request headers to the request exactly as they were sent
 427          if ($headers) {
 428              $parsed = ParserRegistry::getInstance()->getParser('message')->parseRequest($headers);
 429              if (!empty($parsed['headers'])) {
 430                  $request->setHeaders(array());
 431                  foreach ($parsed['headers'] as $name => $value) {
 432                      $request->setHeader($name, $value);
 433                  }
 434              }
 435              if (!empty($parsed['version'])) {
 436                  $request->setProtocolVersion($parsed['version']);
 437              }
 438          }
 439      }
 440  
 441      /**
 442       * Parse the config and replace curl.* configurators into the constant based values so it can be used elsewhere
 443       *
 444       * @param array|Collection $config The configuration we want to parse
 445       *
 446       * @return array
 447       */
 448      public static function parseCurlConfig($config)
 449      {
 450          $curlOptions = array();
 451          foreach ($config as $key => $value) {
 452              if (is_string($key) && defined($key)) {
 453                  // Convert constants represented as string to constant int values
 454                  $key = constant($key);
 455              }
 456              if (is_string($value) && defined($value)) {
 457                  $value = constant($value);
 458              }
 459              $curlOptions[$key] = $value;
 460          }
 461  
 462          return $curlOptions;
 463      }
 464  }

title

Description

title

Description

title

Description

title

title

Body