Verzeichnisstruktur phpBB-3.2.0


Veröffentlicht
06.01.2017

So funktioniert es


Auf das letzte Element klicken. Dies geht jeweils ein Schritt zurück

Auf das Icon klicken, dies öffnet das Verzeichnis. Nochmal klicken schließt das Verzeichnis.
Auf den Verzeichnisnamen klicken, dies zeigt nur das Verzeichnis mit Inhalt an

      (Beispiel Datei-Icons)

Auf das Icon klicken um den Quellcode anzuzeigen

CurlFactory.php

Zuletzt modifiziert: 09.10.2024, 12:57 - Dateigröße: 19.47 KiB


 001  <?php
 002  namespace GuzzleHttp\Ring\Client;
 003   
 004  use GuzzleHttp\Ring\Core;
 005  use GuzzleHttp\Ring\Exception\ConnectException;
 006  use GuzzleHttp\Ring\Exception\RingException;
 007  use GuzzleHttp\Stream\LazyOpenStream;
 008  use GuzzleHttp\Stream\StreamInterface;
 009   
 010  /**
 011   * Creates curl resources from a request
 012   */
 013  class CurlFactory
 014  {
 015      /**
 016       * Creates a cURL handle, header resource, and body resource based on a
 017       * transaction.
 018       *
 019       * @param array         $request Request hash
 020       * @param null|resource $handle  Optionally provide a curl handle to modify
 021       *
 022       * @return array Returns an array of the curl handle, headers array, and
 023       *               response body handle.
 024       * @throws \RuntimeException when an option cannot be applied
 025       */
 026      public function __invoke(array $request, $handle = null)
 027      {
 028          $headers = [];
 029          $options = $this->getDefaultOptions($request, $headers);
 030          $this->applyMethod($request, $options);
 031   
 032          if (isset($request['client'])) {
 033              $this->applyHandlerOptions($request, $options);
 034          }
 035   
 036          $this->applyHeaders($request, $options);
 037          unset($options['_headers']);
 038   
 039          // Add handler options from the request's configuration options
 040          if (isset($request['client']['curl'])) {
 041              $options = $this->applyCustomCurlOptions(
 042                  $request['client']['curl'],
 043                  $options
 044              );
 045          }
 046   
 047          if (!$handle) {
 048              $handle = curl_init();
 049          }
 050   
 051          $body = $this->getOutputBody($request, $options);
 052          curl_setopt_array($handle, $options);
 053   
 054          return [$handle, &$headers, $body];
 055      }
 056   
 057      /**
 058       * Creates a response hash from a cURL result.
 059       *
 060       * @param callable $handler  Handler that was used.
 061       * @param array    $request  Request that sent.
 062       * @param array    $response Response hash to update.
 063       * @param array    $headers  Headers received during transfer.
 064       * @param resource $body     Body fopen response.
 065       *
 066       * @return array
 067       */
 068      public static function createResponse(
 069          callable $handler,
 070          array $request,
 071          array $response,
 072          array $headers,
 073          $body
 074      ) {
 075          if (isset($response['transfer_stats']['url'])) {
 076              $response['effective_url'] = $response['transfer_stats']['url'];
 077          }
 078   
 079          if (!empty($headers)) {
 080              $startLine = explode(' ', array_shift($headers), 3);
 081              $headerList = Core::headersFromLines($headers);
 082              $response['headers'] = $headerList;
 083              $response['version'] = isset($startLine[0]) ? substr($startLine[0], 5) : null;
 084              $response['status'] = isset($startLine[1]) ? (int) $startLine[1] : null;
 085              $response['reason'] = isset($startLine[2]) ? $startLine[2] : null;
 086              $response['body'] = $body;
 087              Core::rewindBody($response);
 088          }
 089   
 090          return !empty($response['curl']['errno']) || !isset($response['status'])
 091              ? self::createErrorResponse($handler, $request, $response)
 092              : $response;
 093      }
 094   
 095      private static function createErrorResponse(
 096          callable $handler,
 097          array $request,
 098          array $response
 099      ) {
 100          static $connectionErrors = [
 101              CURLE_OPERATION_TIMEOUTED  => true,
 102              CURLE_COULDNT_RESOLVE_HOST => true,
 103              CURLE_COULDNT_CONNECT      => true,
 104              CURLE_SSL_CONNECT_ERROR    => true,
 105              CURLE_GOT_NOTHING          => true,
 106          ];
 107   
 108          // Retry when nothing is present or when curl failed to rewind.
 109          if (!isset($response['err_message'])
 110              && (empty($response['curl']['errno'])
 111                  || $response['curl']['errno'] == 65)
 112          ) {
 113              return self::retryFailedRewind($handler, $request, $response);
 114          }
 115   
 116          $message = isset($response['err_message'])
 117              ? $response['err_message']
 118              : sprintf('cURL error %s: %s',
 119                  $response['curl']['errno'],
 120                  isset($response['curl']['error'])
 121                      ? $response['curl']['error']
 122                      : 'See http://curl.haxx.se/libcurl/c/libcurl-errors.html');
 123   
 124          $error = isset($response['curl']['errno'])
 125              && isset($connectionErrors[$response['curl']['errno']])
 126              ? new ConnectException($message)
 127              : new RingException($message);
 128   
 129          return $response + [
 130              'status'  => null,
 131              'reason'  => null,
 132              'body'    => null,
 133              'headers' => [],
 134              'error'   => $error,
 135          ];
 136      }
 137   
 138      private function getOutputBody(array $request, array &$options)
 139      {
 140          // Determine where the body of the response (if any) will be streamed.
 141          if (isset($options[CURLOPT_WRITEFUNCTION])) {
 142              return $request['client']['save_to'];
 143          }
 144   
 145          if (isset($options[CURLOPT_FILE])) {
 146              return $options[CURLOPT_FILE];
 147          }
 148   
 149          if ($request['http_method'] != 'HEAD') {
 150              // Create a default body if one was not provided
 151              return $options[CURLOPT_FILE] = fopen('php://temp', 'w+');
 152          }
 153   
 154          return null;
 155      }
 156   
 157      private function getDefaultOptions(array $request, array &$headers)
 158      {
 159          $url = Core::url($request);
 160          $startingResponse = false;
 161   
 162          $options = [
 163              '_headers'             => $request['headers'],
 164              CURLOPT_CUSTOMREQUEST  => $request['http_method'],
 165              CURLOPT_URL            => $url,
 166              CURLOPT_RETURNTRANSFER => false,
 167              CURLOPT_HEADER         => false,
 168              CURLOPT_CONNECTTIMEOUT => 150,
 169              CURLOPT_HEADERFUNCTION => function ($ch, $h) use (&$headers, &$startingResponse) {
 170                  $value = trim($h);
 171                  if ($value === '') {
 172                      $startingResponse = true;
 173                  } elseif ($startingResponse) {
 174                      $startingResponse = false;
 175                      $headers = [$value];
 176                  } else {
 177                      $headers[] = $value;
 178                  }
 179                  return strlen($h);
 180              },
 181          ];
 182   
 183          if (isset($request['version'])) {
 184              if ($request['version'] == 2.0) {
 185                  $options[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_2_0;
 186              } else if ($request['version'] == 1.1) {
 187                  $options[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_1_1;
 188              } else {
 189                  $options[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_1_0;
 190              }
 191          }
 192   
 193          if (defined('CURLOPT_PROTOCOLS')) {
 194              $options[CURLOPT_PROTOCOLS] = CURLPROTO_HTTP | CURLPROTO_HTTPS;
 195          }
 196   
 197          return $options;
 198      }
 199   
 200      private function applyMethod(array $request, array &$options)
 201      {
 202          if (isset($request['body'])) {
 203              $this->applyBody($request, $options);
 204              return;
 205          }
 206   
 207          switch ($request['http_method']) {
 208              case 'PUT':
 209              case 'POST':
 210                  // See http://tools.ietf.org/html/rfc7230#section-3.3.2
 211                  if (!Core::hasHeader($request, 'Content-Length')) {
 212                      $options[CURLOPT_HTTPHEADER][] = 'Content-Length: 0';
 213                  }
 214                  break;
 215              case 'HEAD':
 216                  $options[CURLOPT_NOBODY] = true;
 217                  unset(
 218                      $options[CURLOPT_WRITEFUNCTION],
 219                      $options[CURLOPT_READFUNCTION],
 220                      $options[CURLOPT_FILE],
 221                      $options[CURLOPT_INFILE]
 222                  );
 223          }
 224      }
 225   
 226      private function applyBody(array $request, array &$options)
 227      {
 228          $contentLength = Core::firstHeader($request, 'Content-Length');
 229          $size = $contentLength !== null ? (int) $contentLength : null;
 230   
 231          // Send the body as a string if the size is less than 1MB OR if the
 232          // [client][curl][body_as_string] request value is set.
 233          if (($size !== null && $size < 1000000) ||
 234              isset($request['client']['curl']['body_as_string']) ||
 235              is_string($request['body'])
 236          ) {
 237              $options[CURLOPT_POSTFIELDS] = Core::body($request);
 238              // Don't duplicate the Content-Length header
 239              $this->removeHeader('Content-Length', $options);
 240              $this->removeHeader('Transfer-Encoding', $options);
 241          } else {
 242              $options[CURLOPT_UPLOAD] = true;
 243              if ($size !== null) {
 244                  // Let cURL handle setting the Content-Length header
 245                  $options[CURLOPT_INFILESIZE] = $size;
 246                  $this->removeHeader('Content-Length', $options);
 247              }
 248              $this->addStreamingBody($request, $options);
 249          }
 250   
 251          // If the Expect header is not present, prevent curl from adding it
 252          if (!Core::hasHeader($request, 'Expect')) {
 253              $options[CURLOPT_HTTPHEADER][] = 'Expect:';
 254          }
 255   
 256          // cURL sometimes adds a content-type by default. Prevent this.
 257          if (!Core::hasHeader($request, 'Content-Type')) {
 258              $options[CURLOPT_HTTPHEADER][] = 'Content-Type:';
 259          }
 260      }
 261   
 262      private function addStreamingBody(array $request, array &$options)
 263      {
 264          $body = $request['body'];
 265   
 266          if ($body instanceof StreamInterface) {
 267              $options[CURLOPT_READFUNCTION] = function ($ch, $fd, $length) use ($body) {
 268                  return (string) $body->read($length);
 269              };
 270              if (!isset($options[CURLOPT_INFILESIZE])) {
 271                  if ($size = $body->getSize()) {
 272                      $options[CURLOPT_INFILESIZE] = $size;
 273                  }
 274              }
 275          } elseif (is_resource($body)) {
 276              $options[CURLOPT_INFILE] = $body;
 277          } elseif ($body instanceof \Iterator) {
 278              $buf = '';
 279              $options[CURLOPT_READFUNCTION] = function ($ch, $fd, $length) use ($body, &$buf) {
 280                  if ($body->valid()) {
 281                      $buf .= $body->current();
 282                      $body->next();
 283                  }
 284                  $result = (string) substr($buf, 0, $length);
 285                  $buf = substr($buf, $length);
 286                  return $result;
 287              };
 288          } else {
 289              throw new \InvalidArgumentException('Invalid request body provided');
 290          }
 291      }
 292   
 293      private function applyHeaders(array $request, array &$options)
 294      {
 295          foreach ($options['_headers'] as $name => $values) {
 296              foreach ($values as $value) {
 297                  $options[CURLOPT_HTTPHEADER][] = "$name: $value";
 298              }
 299          }
 300   
 301          // Remove the Accept header if one was not set
 302          if (!Core::hasHeader($request, 'Accept')) {
 303              $options[CURLOPT_HTTPHEADER][] = 'Accept:';
 304          }
 305      }
 306   
 307      /**
 308       * Takes an array of curl options specified in the 'curl' option of a
 309       * request's configuration array and maps them to CURLOPT_* options.
 310       *
 311       * This method is only called when a  request has a 'curl' config setting.
 312       *
 313       * @param array $config  Configuration array of custom curl option
 314       * @param array $options Array of existing curl options
 315       *
 316       * @return array Returns a new array of curl options
 317       */
 318      private function applyCustomCurlOptions(array $config, array $options)
 319      {
 320          $curlOptions = [];
 321          foreach ($config as $key => $value) {
 322              if (is_int($key)) {
 323                  $curlOptions[$key] = $value;
 324              }
 325          }
 326   
 327          return $curlOptions + $options;
 328      }
 329   
 330      /**
 331       * Remove a header from the options array.
 332       *
 333       * @param string $name    Case-insensitive header to remove
 334       * @param array  $options Array of options to modify
 335       */
 336      private function removeHeader($name, array &$options)
 337      {
 338          foreach (array_keys($options['_headers']) as $key) {
 339              if (!strcasecmp($key, $name)) {
 340                  unset($options['_headers'][$key]);
 341                  return;
 342              }
 343          }
 344      }
 345   
 346      /**
 347       * Applies an array of request client options to a the options array.
 348       *
 349       * This method uses a large switch rather than double-dispatch to save on
 350       * high overhead of calling functions in PHP.
 351       */
 352      private function applyHandlerOptions(array $request, array &$options)
 353      {
 354          foreach ($request['client'] as $key => $value) {
 355              switch ($key) {
 356              // Violating PSR-4 to provide more room.
 357              case 'verify':
 358   
 359                  if ($value === false) {
 360                      unset($options[CURLOPT_CAINFO]);
 361                      $options[CURLOPT_SSL_VERIFYHOST] = 0;
 362                      $options[CURLOPT_SSL_VERIFYPEER] = false;
 363                      continue;
 364                  }
 365   
 366                  $options[CURLOPT_SSL_VERIFYHOST] = 2;
 367                  $options[CURLOPT_SSL_VERIFYPEER] = true;
 368   
 369                  if (is_string($value)) {
 370                      $options[CURLOPT_CAINFO] = $value;
 371                      if (!file_exists($value)) {
 372                          throw new \InvalidArgumentException(
 373                              "SSL CA bundle not found: $value"
 374                          );
 375                      }
 376                  }
 377                  break;
 378   
 379              case 'decode_content':
 380   
 381                  if ($value === false) {
 382                      continue;
 383                  }
 384   
 385                  $accept = Core::firstHeader($request, 'Accept-Encoding');
 386                  if ($accept) {
 387                      $options[CURLOPT_ENCODING] = $accept;
 388                  } else {
 389                      $options[CURLOPT_ENCODING] = '';
 390                      // Don't let curl send the header over the wire
 391                      $options[CURLOPT_HTTPHEADER][] = 'Accept-Encoding:';
 392                  }
 393                  break;
 394   
 395              case 'save_to':
 396   
 397                  if (is_string($value)) {
 398                      if (!is_dir(dirname($value))) {
 399                          throw new \RuntimeException(sprintf(
 400                              'Directory %s does not exist for save_to value of %s',
 401                              dirname($value),
 402                              $value
 403                          ));
 404                      }
 405                      $value = new LazyOpenStream($value, 'w+');
 406                  }
 407   
 408                  if ($value instanceof StreamInterface) {
 409                      $options[CURLOPT_WRITEFUNCTION] =
 410                          function ($ch, $write) use ($value) {
 411                              return $value->write($write);
 412                          };
 413                  } elseif (is_resource($value)) {
 414                      $options[CURLOPT_FILE] = $value;
 415                  } else {
 416                      throw new \InvalidArgumentException('save_to must be a '
 417                          . 'GuzzleHttp\Stream\StreamInterface or resource');
 418                  }
 419                  break;
 420   
 421              case 'timeout':
 422   
 423                  if (defined('CURLOPT_TIMEOUT_MS')) {
 424                      $options[CURLOPT_TIMEOUT_MS] = $value * 1000;
 425                  } else {
 426                      $options[CURLOPT_TIMEOUT] = $value;
 427                  }
 428                  break;
 429   
 430              case 'connect_timeout':
 431   
 432                  if (defined('CURLOPT_CONNECTTIMEOUT_MS')) {
 433                      $options[CURLOPT_CONNECTTIMEOUT_MS] = $value * 1000;
 434                  } else {
 435                      $options[CURLOPT_CONNECTTIMEOUT] = $value;
 436                  }
 437                  break;
 438   
 439              case 'proxy':
 440   
 441                  if (!is_array($value)) {
 442                      $options[CURLOPT_PROXY] = $value;
 443                  } elseif (isset($request['scheme'])) {
 444                      $scheme = $request['scheme'];
 445                      if (isset($value[$scheme])) {
 446                          $options[CURLOPT_PROXY] = $value[$scheme];
 447                      }
 448                  }
 449                  break;
 450   
 451              case 'cert':
 452   
 453                  if (is_array($value)) {
 454                      $options[CURLOPT_SSLCERTPASSWD] = $value[1];
 455                      $value = $value[0];
 456                  }
 457   
 458                  if (!file_exists($value)) {
 459                      throw new \InvalidArgumentException(
 460                          "SSL certificate not found: {$value}"
 461                      );
 462                  }
 463   
 464                  $options[CURLOPT_SSLCERT] = $value;
 465                  break;
 466   
 467              case 'ssl_key':
 468   
 469                  if (is_array($value)) {
 470                      $options[CURLOPT_SSLKEYPASSWD] = $value[1];
 471                      $value = $value[0];
 472                  }
 473   
 474                  if (!file_exists($value)) {
 475                      throw new \InvalidArgumentException(
 476                          "SSL private key not found: {$value}"
 477                      );
 478                  }
 479   
 480                  $options[CURLOPT_SSLKEY] = $value;
 481                  break;
 482   
 483              case 'progress':
 484   
 485                  if (!is_callable($value)) {
 486                      throw new \InvalidArgumentException(
 487                          'progress client option must be callable'
 488                      );
 489                  }
 490   
 491                  $options[CURLOPT_NOPROGRESS] = false;
 492                  $options[CURLOPT_PROGRESSFUNCTION] =
 493                      function () use ($value) {
 494                          $args = func_get_args();
 495                          // PHP 5.5 pushed the handle onto the start of the args
 496                          if (is_resource($args[0])) {
 497                              array_shift($args);
 498                          }
 499                          call_user_func_array($value, $args);
 500                      };
 501                  break;
 502   
 503              case 'debug':
 504   
 505                  if ($value) {
 506                      $options[CURLOPT_STDERR] = Core::getDebugResource($value);
 507                      $options[CURLOPT_VERBOSE] = true;
 508                  }
 509                  break;
 510              }
 511          }
 512      }
 513   
 514      /**
 515       * This function ensures that a response was set on a transaction. If one
 516       * was not set, then the request is retried if possible. This error
 517       * typically means you are sending a payload, curl encountered a
 518       * "Connection died, retrying a fresh connect" error, tried to rewind the
 519       * stream, and then encountered a "necessary data rewind wasn't possible"
 520       * error, causing the request to be sent through curl_multi_info_read()
 521       * without an error status.
 522       */
 523      private static function retryFailedRewind(
 524          callable $handler,
 525          array $request,
 526          array $response
 527      ) {
 528          // If there is no body, then there is some other kind of issue. This
 529          // is weird and should probably never happen.
 530          if (!isset($request['body'])) {
 531              $response['err_message'] = 'No response was received for a request '
 532                  . 'with no body. This could mean that you are saturating your '
 533                  . 'network.';
 534              return self::createErrorResponse($handler, $request, $response);
 535          }
 536   
 537          if (!Core::rewindBody($request)) {
 538              $response['err_message'] = 'The connection unexpectedly failed '
 539                  . 'without providing an error. The request would have been '
 540                  . 'retried, but attempting to rewind the request body failed.';
 541              return self::createErrorResponse($handler, $request, $response);
 542          }
 543   
 544          // Retry no more than 3 times before giving up.
 545          if (!isset($request['curl']['retries'])) {
 546              $request['curl']['retries'] = 1;
 547          } elseif ($request['curl']['retries'] == 2) {
 548              $response['err_message'] = 'The cURL request was retried 3 times '
 549                  . 'and did no succeed. cURL was unable to rewind the body of '
 550                  . 'the request and subsequent retries resulted in the same '
 551                  . 'error. Turn on the debug option to see what went wrong. '
 552                  . 'See https://bugs.php.net/bug.php?id=47204 for more information.';
 553              return self::createErrorResponse($handler, $request, $response);
 554          } else {
 555              $request['curl']['retries']++;
 556          }
 557   
 558          return $handler($request);
 559      }
 560  }
 561