vendor/stripe/stripe-php/lib/HttpClient/CurlClient.php line 290

Open in your IDE?
  1. <?php
  2. namespace Stripe\HttpClient;
  3. use Stripe\Exception;
  4. use Stripe\Stripe;
  5. use Stripe\Util;
  6. // @codingStandardsIgnoreStart
  7. // PSR2 requires all constants be upper case. Sadly, the CURL_SSLVERSION
  8. // constants do not abide by those rules.
  9. // Note the values come from their position in the enums that
  10. // defines them in cURL's source code.
  11. // Available since PHP 5.5.19 and 5.6.3
  12. if (!\defined('CURL_SSLVERSION_TLSv1_2')) {
  13.     \define('CURL_SSLVERSION_TLSv1_2'6);
  14. }
  15. // @codingStandardsIgnoreEnd
  16. // Available since PHP 7.0.7 and cURL 7.47.0
  17. if (!\defined('CURL_HTTP_VERSION_2TLS')) {
  18.     \define('CURL_HTTP_VERSION_2TLS'4);
  19. }
  20. class CurlClient implements ClientInterfaceStreamingClientInterface
  21. {
  22.     protected static $instance;
  23.     public static function instance()
  24.     {
  25.         if (!static::$instance) {
  26.             static::$instance = new static();
  27.         }
  28.         return static::$instance;
  29.     }
  30.     protected $defaultOptions;
  31.     /** @var \Stripe\Util\RandomGenerator */
  32.     protected $randomGenerator;
  33.     protected $userAgentInfo;
  34.     protected $enablePersistentConnections true;
  35.     protected $enableHttp2;
  36.     protected $curlHandle;
  37.     protected $requestStatusCallback;
  38.     /**
  39.      * CurlClient constructor.
  40.      *
  41.      * Pass in a callable to $defaultOptions that returns an array of CURLOPT_* values to start
  42.      * off a request with, or an flat array with the same format used by curl_setopt_array() to
  43.      * provide a static set of options. Note that many options are overridden later in the request
  44.      * call, including timeouts, which can be set via setTimeout() and setConnectTimeout().
  45.      *
  46.      * Note that request() will silently ignore a non-callable, non-array $defaultOptions, and will
  47.      * throw an exception if $defaultOptions returns a non-array value.
  48.      *
  49.      * @param null|array|callable $defaultOptions
  50.      * @param null|\Stripe\Util\RandomGenerator $randomGenerator
  51.      */
  52.     public function __construct($defaultOptions null$randomGenerator null)
  53.     {
  54.         $this->defaultOptions $defaultOptions;
  55.         $this->randomGenerator $randomGenerator ?: new Util\RandomGenerator();
  56.         $this->initUserAgentInfo();
  57.         $this->enableHttp2 $this->canSafelyUseHttp2();
  58.     }
  59.     public function __destruct()
  60.     {
  61.         $this->closeCurlHandle();
  62.     }
  63.     public function initUserAgentInfo()
  64.     {
  65.         $curlVersion = \curl_version();
  66.         $this->userAgentInfo = [
  67.             'httplib' => 'curl ' $curlVersion['version'],
  68.             'ssllib' => $curlVersion['ssl_version'],
  69.         ];
  70.     }
  71.     public function getDefaultOptions()
  72.     {
  73.         return $this->defaultOptions;
  74.     }
  75.     public function getUserAgentInfo()
  76.     {
  77.         return $this->userAgentInfo;
  78.     }
  79.     /**
  80.      * @return bool
  81.      */
  82.     public function getEnablePersistentConnections()
  83.     {
  84.         return $this->enablePersistentConnections;
  85.     }
  86.     /**
  87.      * @param bool $enable
  88.      */
  89.     public function setEnablePersistentConnections($enable)
  90.     {
  91.         $this->enablePersistentConnections $enable;
  92.     }
  93.     /**
  94.      * @return bool
  95.      */
  96.     public function getEnableHttp2()
  97.     {
  98.         return $this->enableHttp2;
  99.     }
  100.     /**
  101.      * @param bool $enable
  102.      */
  103.     public function setEnableHttp2($enable)
  104.     {
  105.         $this->enableHttp2 $enable;
  106.     }
  107.     /**
  108.      * @return null|callable
  109.      */
  110.     public function getRequestStatusCallback()
  111.     {
  112.         return $this->requestStatusCallback;
  113.     }
  114.     /**
  115.      * Sets a callback that is called after each request. The callback will
  116.      * receive the following parameters:
  117.      * <ol>
  118.      *   <li>string $rbody The response body</li>
  119.      *   <li>integer $rcode The response status code</li>
  120.      *   <li>\Stripe\Util\CaseInsensitiveArray $rheaders The response headers</li>
  121.      *   <li>integer $errno The curl error number</li>
  122.      *   <li>string|null $message The curl error message</li>
  123.      *   <li>boolean $shouldRetry Whether the request will be retried</li>
  124.      *   <li>integer $numRetries The number of the retry attempt</li>
  125.      * </ol>.
  126.      *
  127.      * @param null|callable $requestStatusCallback
  128.      */
  129.     public function setRequestStatusCallback($requestStatusCallback)
  130.     {
  131.         $this->requestStatusCallback $requestStatusCallback;
  132.     }
  133.     // USER DEFINED TIMEOUTS
  134.     const DEFAULT_TIMEOUT 80;
  135.     const DEFAULT_CONNECT_TIMEOUT 30;
  136.     private $timeout self::DEFAULT_TIMEOUT;
  137.     private $connectTimeout self::DEFAULT_CONNECT_TIMEOUT;
  138.     public function setTimeout($seconds)
  139.     {
  140.         $this->timeout = (int) \max($seconds0);
  141.         return $this;
  142.     }
  143.     public function setConnectTimeout($seconds)
  144.     {
  145.         $this->connectTimeout = (int) \max($seconds0);
  146.         return $this;
  147.     }
  148.     public function getTimeout()
  149.     {
  150.         return $this->timeout;
  151.     }
  152.     public function getConnectTimeout()
  153.     {
  154.         return $this->connectTimeout;
  155.     }
  156.     // END OF USER DEFINED TIMEOUTS
  157.     private function constructRequest($method$absUrl$headers$params$hasFile)
  158.     {
  159.         $method = \strtolower($method);
  160.         $opts = [];
  161.         if (\is_callable($this->defaultOptions)) { // call defaultOptions callback, set options to return value
  162.             $opts = \call_user_func_array($this->defaultOptions, \func_get_args());
  163.             if (!\is_array($opts)) {
  164.                 throw new Exception\UnexpectedValueException('Non-array value returned by defaultOptions CurlClient callback');
  165.             }
  166.         } elseif (\is_array($this->defaultOptions)) { // set default curlopts from array
  167.             $opts $this->defaultOptions;
  168.         }
  169.         $params Util\Util::objectsToIds($params);
  170.         if ('get' === $method) {
  171.             if ($hasFile) {
  172.                 throw new Exception\UnexpectedValueException(
  173.                     'Issuing a GET request with a file parameter'
  174.                 );
  175.             }
  176.             $opts[\CURLOPT_HTTPGET] = 1;
  177.             if (\count($params) > 0) {
  178.                 $encoded Util\Util::encodeParameters($params);
  179.                 $absUrl "{$absUrl}?{$encoded}";
  180.             }
  181.         } elseif ('post' === $method) {
  182.             $opts[\CURLOPT_POST] = 1;
  183.             $opts[\CURLOPT_POSTFIELDS] = $hasFile $params Util\Util::encodeParameters($params);
  184.         } elseif ('delete' === $method) {
  185.             $opts[\CURLOPT_CUSTOMREQUEST] = 'DELETE';
  186.             if (\count($params) > 0) {
  187.                 $encoded Util\Util::encodeParameters($params);
  188.                 $absUrl "{$absUrl}?{$encoded}";
  189.             }
  190.         } else {
  191.             throw new Exception\UnexpectedValueException("Unrecognized method {$method}");
  192.         }
  193.         // It is only safe to retry network failures on POST requests if we
  194.         // add an Idempotency-Key header
  195.         if (('post' === $method) && (Stripe::$maxNetworkRetries 0)) {
  196.             if (!$this->hasHeader($headers'Idempotency-Key')) {
  197.                 $headers[] = 'Idempotency-Key: ' $this->randomGenerator->uuid();
  198.             }
  199.         }
  200.         // By default for large request body sizes (> 1024 bytes), cURL will
  201.         // send a request without a body and with a `Expect: 100-continue`
  202.         // header, which gives the server a chance to respond with an error
  203.         // status code in cases where one can be determined right away (say
  204.         // on an authentication problem for example), and saves the "large"
  205.         // request body from being ever sent.
  206.         //
  207.         // Unfortunately, the bindings don't currently correctly handle the
  208.         // success case (in which the server sends back a 100 CONTINUE), so
  209.         // we'll error under that condition. To compensate for that problem
  210.         // for the time being, override cURL's behavior by simply always
  211.         // sending an empty `Expect:` header.
  212.         $headers[] = 'Expect: ';
  213.         $absUrl Util\Util::utf8($absUrl);
  214.         $opts[\CURLOPT_URL] = $absUrl;
  215.         $opts[\CURLOPT_RETURNTRANSFER] = true;
  216.         $opts[\CURLOPT_CONNECTTIMEOUT] = $this->connectTimeout;
  217.         $opts[\CURLOPT_TIMEOUT] = $this->timeout;
  218.         $opts[\CURLOPT_HTTPHEADER] = $headers;
  219.         $opts[\CURLOPT_CAINFO] = Stripe::getCABundlePath();
  220.         if (!Stripe::getVerifySslCerts()) {
  221.             $opts[\CURLOPT_SSL_VERIFYPEER] = false;
  222.         }
  223.         if (!isset($opts[\CURLOPT_HTTP_VERSION]) && $this->getEnableHttp2()) {
  224.             // For HTTPS requests, enable HTTP/2, if supported
  225.             $opts[\CURLOPT_HTTP_VERSION] = \CURL_HTTP_VERSION_2TLS;
  226.         }
  227.         // If the user didn't explicitly specify a CURLOPT_IPRESOLVE option, we
  228.         // force IPv4 resolving as Stripe's API servers are only accessible over
  229.         // IPv4 (see. https://github.com/stripe/stripe-php/issues/1045).
  230.         // We let users specify a custom option in case they need to say proxy
  231.         // through an IPv6 proxy.
  232.         if (!isset($opts[\CURLOPT_IPRESOLVE])) {
  233.             $opts[\CURLOPT_IPRESOLVE] = \CURL_IPRESOLVE_V4;
  234.         }
  235.         return [$opts$absUrl];
  236.     }
  237.     public function request($method$absUrl$headers$params$hasFile)
  238.     {
  239.         list($opts$absUrl) = $this->constructRequest($method$absUrl$headers$params$hasFile);
  240.         list($rbody$rcode$rheaders) = $this->executeRequestWithRetries($opts$absUrl);
  241.         return [$rbody$rcode$rheaders];
  242.     }
  243.     public function requestStream($method$absUrl$headers$params$hasFile$readBodyChunk)
  244.     {
  245.         list($opts$absUrl) = $this->constructRequest($method$absUrl$headers$params$hasFile);
  246.         $opts[\CURLOPT_RETURNTRANSFER] = false;
  247.         list($rbody$rcode$rheaders) = $this->executeStreamingRequestWithRetries($opts$absUrl$readBodyChunk);
  248.         return [$rbody$rcode$rheaders];
  249.     }
  250.     /**
  251.      * Curl permits sending \CURLOPT_HEADERFUNCTION, which is called with lines
  252.      * from the header and \CURLOPT_WRITEFUNCTION, which is called with bytes
  253.      * from the body. You usually want to handle the body differently depending
  254.      * on what was in the header.
  255.      *
  256.      * This function makes it easier to specify different callbacks depending
  257.      * on the contents of the heeder. After the header has been completely read
  258.      * and the body begins to stream, it will call $determineWriteCallback with
  259.      * the array of headers. $determineWriteCallback should, based on the
  260.      * headers it receives, return a "writeCallback" that describes what to do
  261.      * with the incoming HTTP response body.
  262.      *
  263.      * @param array $opts
  264.      * @param callable $determineWriteCallback
  265.      *
  266.      * @return array
  267.      */
  268.     private function useHeadersToDetermineWriteCallback($opts$determineWriteCallback)
  269.     {
  270.         $rheaders = new Util\CaseInsensitiveArray();
  271.         $headerCallback = function ($curl$header_line) use (&$rheaders) {
  272.             return self::parseLineIntoHeaderArray($header_line$rheaders);
  273.         };
  274.         $writeCallback null;
  275.         $writeCallbackWrapper = function ($curl$data) use (&$writeCallback, &$rheaders, &$determineWriteCallback) {
  276.             if (null === $writeCallback) {
  277.                 $writeCallback = \call_user_func_array($determineWriteCallback, [$rheaders]);
  278.             }
  279.             return \call_user_func_array($writeCallback, [$curl$data]);
  280.         };
  281.         return [$headerCallback$writeCallbackWrapper];
  282.     }
  283.     private static function parseLineIntoHeaderArray($line, &$headers)
  284.     {
  285.         if (false === \strpos($line':')) {
  286.             return \strlen($line);
  287.         }
  288.         list($key$value) = \explode(':', \trim($line), 2);
  289.         $headers[\trim($key)] = \trim($value);
  290.         return \strlen($line);
  291.     }
  292.     /**
  293.      * Like `executeRequestWithRetries` except:
  294.      *   1. Does not buffer the body of a successful (status code < 300)
  295.      *      response into memory -- instead, calls the caller-provided
  296.      *      $readBodyChunk with each chunk of incoming data.
  297.      *   2. Does not retry if a network error occurs while streaming the
  298.      *      body of a successful response.
  299.      *
  300.      * @param array $opts cURL options
  301.      * @param string $absUrl
  302.      * @param callable $readBodyChunk
  303.      *
  304.      * @return array
  305.      */
  306.     public function executeStreamingRequestWithRetries($opts$absUrl$readBodyChunk)
  307.     {
  308.         /** @var bool */
  309.         $shouldRetry false;
  310.         /** @var int */
  311.         $numRetries 0;
  312.         // Will contain the bytes of the body of the last request
  313.         // if it was not successful and should not be retries
  314.         /** @var null|string */
  315.         $rbody null;
  316.         // Status code of the last request
  317.         /** @var null|bool */
  318.         $rcode null;
  319.         // Array of headers from the last request
  320.         /** @var null|array */
  321.         $lastRHeaders null;
  322.         $errno null;
  323.         $message null;
  324.         $determineWriteCallback = function ($rheaders) use (
  325.             &$readBodyChunk,
  326.             &$shouldRetry,
  327.             &$rbody,
  328.             &$numRetries,
  329.             &$rcode,
  330.             &$lastRHeaders,
  331.             &$errno
  332.         ) {
  333.             $lastRHeaders $rheaders;
  334.             $errno = \curl_errno($this->curlHandle);
  335.             $rcode = \curl_getinfo($this->curlHandle, \CURLINFO_HTTP_CODE);
  336.             // Send the bytes from the body of a successful request to the caller-provided $readBodyChunk.
  337.             if ($rcode 300) {
  338.                 $rbody null;
  339.                 return function ($curl$data) use (&$readBodyChunk) {
  340.                     // Don't expose the $curl handle to the user, and don't require them to
  341.                     // return the length of $data.
  342.                     \call_user_func_array($readBodyChunk, [$data]);
  343.                     return \strlen($data);
  344.                 };
  345.             }
  346.             $shouldRetry $this->shouldRetry($errno$rcode$rheaders$numRetries);
  347.             // Discard the body from an unsuccessful request that should be retried.
  348.             if ($shouldRetry) {
  349.                 return function ($curl$data) {
  350.                     return \strlen($data);
  351.                 };
  352.             } else {
  353.                 // Otherwise, buffer the body into $rbody. It will need to be parsed to determine
  354.                 // which exception to throw to the user.
  355.                 $rbody '';
  356.                 return function ($curl$data) use (&$rbody) {
  357.                     $rbody .= $data;
  358.                     return \strlen($data);
  359.                 };
  360.             }
  361.         };
  362.         while (true) {
  363.             list($headerCallback$writeCallback) = $this->useHeadersToDetermineWriteCallback($opts$determineWriteCallback);
  364.             $opts[\CURLOPT_HEADERFUNCTION] = $headerCallback;
  365.             $opts[\CURLOPT_WRITEFUNCTION] = $writeCallback;
  366.             $shouldRetry false;
  367.             $rbody null;
  368.             $this->resetCurlHandle();
  369.             \curl_setopt_array($this->curlHandle$opts);
  370.             $result = \curl_exec($this->curlHandle);
  371.             $errno = \curl_errno($this->curlHandle);
  372.             if (!== $errno) {
  373.                 $message = \curl_error($this->curlHandle);
  374.             }
  375.             if (!$this->getEnablePersistentConnections()) {
  376.                 $this->closeCurlHandle();
  377.             }
  378.             if (\is_callable($this->getRequestStatusCallback())) {
  379.                 \call_user_func_array(
  380.                     $this->getRequestStatusCallback(),
  381.                     [$rbody$rcode$lastRHeaders$errno$message$shouldRetry$numRetries]
  382.                 );
  383.             }
  384.             if ($shouldRetry) {
  385.                 ++$numRetries;
  386.                 $sleepSeconds $this->sleepTime($numRetries$lastRHeaders);
  387.                 \usleep((int) ($sleepSeconds 1000000));
  388.             } else {
  389.                 break;
  390.             }
  391.         }
  392.         if (!== $errno) {
  393.             $this->handleCurlError($absUrl$errno$message$numRetries);
  394.         }
  395.         return [$rbody$rcode$lastRHeaders];
  396.     }
  397.     /**
  398.      * @param array $opts cURL options
  399.      * @param string $absUrl
  400.      */
  401.     public function executeRequestWithRetries($opts$absUrl)
  402.     {
  403.         $numRetries 0;
  404.         while (true) {
  405.             $rcode 0;
  406.             $errno 0;
  407.             $message null;
  408.             // Create a callback to capture HTTP headers for the response
  409.             $rheaders = new Util\CaseInsensitiveArray();
  410.             $headerCallback = function ($curl$header_line) use (&$rheaders) {
  411.                 return CurlClient::parseLineIntoHeaderArray($header_line$rheaders);
  412.             };
  413.             $opts[\CURLOPT_HEADERFUNCTION] = $headerCallback;
  414.             $this->resetCurlHandle();
  415.             \curl_setopt_array($this->curlHandle$opts);
  416.             $rbody = \curl_exec($this->curlHandle);
  417.             if (false === $rbody) {
  418.                 $errno = \curl_errno($this->curlHandle);
  419.                 $message = \curl_error($this->curlHandle);
  420.             } else {
  421.                 $rcode = \curl_getinfo($this->curlHandle, \CURLINFO_HTTP_CODE);
  422.             }
  423.             if (!$this->getEnablePersistentConnections()) {
  424.                 $this->closeCurlHandle();
  425.             }
  426.             $shouldRetry $this->shouldRetry($errno$rcode$rheaders$numRetries);
  427.             if (\is_callable($this->getRequestStatusCallback())) {
  428.                 \call_user_func_array(
  429.                     $this->getRequestStatusCallback(),
  430.                     [$rbody$rcode$rheaders$errno$message$shouldRetry$numRetries]
  431.                 );
  432.             }
  433.             if ($shouldRetry) {
  434.                 ++$numRetries;
  435.                 $sleepSeconds $this->sleepTime($numRetries$rheaders);
  436.                 \usleep((int) ($sleepSeconds 1000000));
  437.             } else {
  438.                 break;
  439.             }
  440.         }
  441.         if (false === $rbody) {
  442.             $this->handleCurlError($absUrl$errno$message$numRetries);
  443.         }
  444.         return [$rbody$rcode$rheaders];
  445.     }
  446.     /**
  447.      * @param string $url
  448.      * @param int $errno
  449.      * @param string $message
  450.      * @param int $numRetries
  451.      *
  452.      * @throws Exception\ApiConnectionException
  453.      */
  454.     private function handleCurlError($url$errno$message$numRetries)
  455.     {
  456.         switch ($errno) {
  457.             case \CURLE_COULDNT_CONNECT:
  458.             case \CURLE_COULDNT_RESOLVE_HOST:
  459.             case \CURLE_OPERATION_TIMEOUTED:
  460.                 $msg "Could not connect to Stripe ({$url}).  Please check your "
  461.                  'internet connection and try again.  If this problem persists, '
  462.                  "you should check Stripe's service status at "
  463.                  'https://twitter.com/stripestatus, or';
  464.                 break;
  465.             case \CURLE_SSL_CACERT:
  466.             case \CURLE_SSL_PEER_CERTIFICATE:
  467.                 $msg "Could not verify Stripe's SSL certificate.  Please make sure "
  468.                  'that your network is not intercepting certificates.  '
  469.                  "(Try going to {$url} in your browser.)  "
  470.                  'If this problem persists,';
  471.                 break;
  472.             default:
  473.                 $msg 'Unexpected error communicating with Stripe.  '
  474.                  'If this problem persists,';
  475.         }
  476.         $msg .= ' let us know at support@stripe.com.';
  477.         $msg .= "\n\n(Network error [errno {$errno}]: {$message})";
  478.         if ($numRetries 0) {
  479.             $msg .= "\n\nRequest was retried {$numRetries} times.";
  480.         }
  481.         throw new Exception\ApiConnectionException($msg);
  482.     }
  483.     /**
  484.      * Checks if an error is a problem that we should retry on. This includes both
  485.      * socket errors that may represent an intermittent problem and some special
  486.      * HTTP statuses.
  487.      *
  488.      * @param int $errno
  489.      * @param int $rcode
  490.      * @param array|\Stripe\Util\CaseInsensitiveArray $rheaders
  491.      * @param int $numRetries
  492.      *
  493.      * @return bool
  494.      */
  495.     private function shouldRetry($errno$rcode$rheaders$numRetries)
  496.     {
  497.         if ($numRetries >= Stripe::getMaxNetworkRetries()) {
  498.             return false;
  499.         }
  500.         // Retry on timeout-related problems (either on open or read).
  501.         if (\CURLE_OPERATION_TIMEOUTED === $errno) {
  502.             return true;
  503.         }
  504.         // Destination refused the connection, the connection was reset, or a
  505.         // variety of other connection failures. This could occur from a single
  506.         // saturated server, so retry in case it's intermittent.
  507.         if (\CURLE_COULDNT_CONNECT === $errno) {
  508.             return true;
  509.         }
  510.         // The API may ask us not to retry (eg; if doing so would be a no-op)
  511.         // or advise us to retry (eg; in cases of lock timeouts); we defer to that.
  512.         if (isset($rheaders['stripe-should-retry'])) {
  513.             if ('false' === $rheaders['stripe-should-retry']) {
  514.                 return false;
  515.             }
  516.             if ('true' === $rheaders['stripe-should-retry']) {
  517.                 return true;
  518.             }
  519.         }
  520.         // 409 Conflict
  521.         if (409 === $rcode) {
  522.             return true;
  523.         }
  524.         // Retry on 500, 503, and other internal errors.
  525.         //
  526.         // Note that we expect the stripe-should-retry header to be false
  527.         // in most cases when a 500 is returned, since our idempotency framework
  528.         // would typically replay it anyway.
  529.         if ($rcode >= 500) {
  530.             return true;
  531.         }
  532.         return false;
  533.     }
  534.     /**
  535.      * Provides the number of seconds to wait before retrying a request.
  536.      *
  537.      * @param int $numRetries
  538.      * @param array|\Stripe\Util\CaseInsensitiveArray $rheaders
  539.      *
  540.      * @return int
  541.      */
  542.     private function sleepTime($numRetries$rheaders)
  543.     {
  544.         // Apply exponential backoff with $initialNetworkRetryDelay on the
  545.         // number of $numRetries so far as inputs. Do not allow the number to exceed
  546.         // $maxNetworkRetryDelay.
  547.         $sleepSeconds = \min(
  548.             Stripe::getInitialNetworkRetryDelay() * 1.0 ** ($numRetries 1),
  549.             Stripe::getMaxNetworkRetryDelay()
  550.         );
  551.         // Apply some jitter by randomizing the value in the range of
  552.         // ($sleepSeconds / 2) to ($sleepSeconds).
  553.         $sleepSeconds *= 0.5 * ($this->randomGenerator->randFloat());
  554.         // But never sleep less than the base sleep seconds.
  555.         $sleepSeconds = \max(Stripe::getInitialNetworkRetryDelay(), $sleepSeconds);
  556.         // And never sleep less than the time the API asks us to wait, assuming it's a reasonable ask.
  557.         $retryAfter = isset($rheaders['retry-after']) ? (float) ($rheaders['retry-after']) : 0.0;
  558.         if (\floor($retryAfter) === $retryAfter && $retryAfter <= Stripe::getMaxRetryAfter()) {
  559.             $sleepSeconds = \max($sleepSeconds$retryAfter);
  560.         }
  561.         return $sleepSeconds;
  562.     }
  563.     /**
  564.      * Initializes the curl handle. If already initialized, the handle is closed first.
  565.      */
  566.     private function initCurlHandle()
  567.     {
  568.         $this->closeCurlHandle();
  569.         $this->curlHandle = \curl_init();
  570.     }
  571.     /**
  572.      * Closes the curl handle if initialized. Do nothing if already closed.
  573.      */
  574.     private function closeCurlHandle()
  575.     {
  576.         if (null !== $this->curlHandle) {
  577.             \curl_close($this->curlHandle);
  578.             $this->curlHandle null;
  579.         }
  580.     }
  581.     /**
  582.      * Resets the curl handle. If the handle is not already initialized, or if persistent
  583.      * connections are disabled, the handle is reinitialized instead.
  584.      */
  585.     private function resetCurlHandle()
  586.     {
  587.         if (null !== $this->curlHandle && $this->getEnablePersistentConnections()) {
  588.             \curl_reset($this->curlHandle);
  589.         } else {
  590.             $this->initCurlHandle();
  591.         }
  592.     }
  593.     /**
  594.      * Indicates whether it is safe to use HTTP/2 or not.
  595.      *
  596.      * @return bool
  597.      */
  598.     private function canSafelyUseHttp2()
  599.     {
  600.         // Versions of curl older than 7.60.0 don't respect GOAWAY frames
  601.         // (cf. https://github.com/curl/curl/issues/2416), which Stripe use.
  602.         $curlVersion = \curl_version()['version'];
  603.         return \version_compare($curlVersion'7.60.0') >= 0;
  604.     }
  605.     /**
  606.      * Checks if a list of headers contains a specific header name.
  607.      *
  608.      * @param string[] $headers
  609.      * @param string $name
  610.      *
  611.      * @return bool
  612.      */
  613.     private function hasHeader($headers$name)
  614.     {
  615.         foreach ($headers as $header) {
  616.             if (=== \strncasecmp($header"{$name}: ", \strlen($name) + 2)) {
  617.                 return true;
  618.             }
  619.         }
  620.         return false;
  621.     }
  622. }