PluginProbe ʕ •ᴥ•ʔ
GiveWP – Donation Plugin and Fundraising Platform / 4.16.3
GiveWP – Donation Plugin and Fundraising Platform v4.16.3
4.16.3 4.16.2 4.16.1 4.16.0 4.15.5 4.15.4 4.15.3 4.15.2 4.15.1 4.15.0 2.3.0 2.3.1 2.3.2 2.30.0 2.31.0 2.31.1 2.32.0 2.33.0 2.33.1 2.33.2 2.33.3 2.33.4 2.33.5 2.4.0 2.4.1 2.4.2 2.4.3 2.4.4 2.4.5 2.4.6 2.4.7 2.5.0 2.5.1 2.5.10 2.5.11 2.5.12 2.5.13 2.5.2 2.5.3 2.5.4 2.5.5 2.5.6 2.5.7 2.5.8 2.5.9 2.6.0 2.6.1 2.6.2 2.6.3 2.7.0 2.7.1 2.7.2 2.7.3 2.7.4 2.7.5 2.8.0 2.8.1 2.9.0 2.9.1 2.9.2 2.9.3 2.9.4 2.9.5 2.9.6 2.9.7 3.0.0 3.0.1 3.0.2 3.0.3 3.0.4 3.1.0 3.1.1 3.1.2 3.10.0 3.11.0 3.12.0 3.12.1 3.12.2 3.12.3 3.13.0 3.14.0 3.14.1 3.14.2 3.15.0 3.15.1 3.16.0 3.16.1 3.16.2 3.16.3 3.16.4 3.16.5 3.17.0 3.17.1 3.17.2 3.18.0 3.19.0 3.19.1 3.19.2 3.19.3 3.19.4 3.2.0 3.2.1 3.2.2 3.20.0 3.21.0 3.21.1 3.22.0 3.22.1 3.22.2 3.3.0 3.3.1 3.4.0 3.4.1 3.4.2 3.5.0 3.5.1 3.6.0 3.6.1 3.6.2 3.7.0 3.8.0 3.9.0 4.0.0 4.1.0 4.1.1 4.10.0 4.10.1 4.11.0 4.12.0 4.13.0 4.13.1 4.13.2 4.14.0 4.14.1 4.14.2 4.14.3 4.14.4 4.14.5 4.14.6 4.2.0 4.2.1 4.3.0 4.3.1 4.3.2 4.4.0 4.5.0 4.6.1 4.7.0 4.7.1 4.8.0 4.8.1 4.9.0 trunk 1.9.0 2.0.0 2.0.1 2.0.2 2.0.3 2.0.4 2.0.5 2.0.6 2.0.7 2.1.0 2.1.1 2.1.2 2.1.3 2.1.4 2.1.5 2.1.6 2.1.7 2.1.8 2.10.0 2.10.1 2.10.2 2.10.3 2.10.4 2.11.0 2.11.1 2.11.2 2.11.3 2.12.0 2.12.1 2.12.2 2.12.3 2.13.0 2.13.1 2.13.2 2.13.3 2.13.4 2.14.0 2.15.0 2.16.0 2.16.1 2.17.0 2.17.1 2.17.3 2.18.0 2.18.1 2.19.1 2.19.2 2.19.3 2.19.4 2.19.5 2.19.6 2.19.7 2.19.8 2.2.0 2.2.1 2.2.2 2.2.3 2.2.4 2.2.5 2.2.6 2.20.0 2.20.1 2.20.2 2.21.0 2.21.1 2.21.2 2.21.3 2.21.4 2.22.0 2.22.1 2.22.2 2.22.3 2.23.0 2.23.1 2.23.2 2.24.0 2.24.1 2.24.2 2.25.0 2.25.1 2.25.2 2.25.3 2.26.0 2.27.0 2.27.1 2.27.2 2.27.3 2.28.0 2.29.0 2.29.1 2.29.2
give / vendor / stripe / stripe-php / lib / HttpClient / CurlClient.php
give / vendor / stripe / stripe-php / lib / HttpClient Last commit date
ClientInterface.php 4 years ago CurlClient.php 2 years ago StreamingClientInterface.php 4 years ago
CurlClient.php
740 lines
1 <?php
2
3 namespace Stripe\HttpClient;
4
5 use Stripe\Exception;
6 use Stripe\Stripe;
7 use Stripe\Util;
8
9 // @codingStandardsIgnoreStart
10 // PSR2 requires all constants be upper case. Sadly, the CURL_SSLVERSION
11 // constants do not abide by those rules.
12
13 // Note the values come from their position in the enums that
14 // defines them in cURL's source code.
15
16 // Available since PHP 5.5.19 and 5.6.3
17 if (!\defined('CURL_SSLVERSION_TLSv1_2')) {
18 \define('CURL_SSLVERSION_TLSv1_2', 6);
19 }
20 // @codingStandardsIgnoreEnd
21
22 // Available since PHP 7.0.7 and cURL 7.47.0
23 if (!\defined('CURL_HTTP_VERSION_2TLS')) {
24 \define('CURL_HTTP_VERSION_2TLS', 4);
25 }
26
27 class CurlClient implements ClientInterface, StreamingClientInterface
28 {
29 protected static $instance;
30
31 public static function instance()
32 {
33 if (!static::$instance) {
34 static::$instance = new static();
35 }
36
37 return static::$instance;
38 }
39
40 protected $defaultOptions;
41
42 /** @var \Stripe\Util\RandomGenerator */
43 protected $randomGenerator;
44
45 protected $userAgentInfo;
46
47 protected $enablePersistentConnections = true;
48
49 protected $enableHttp2;
50
51 protected $curlHandle;
52
53 protected $requestStatusCallback;
54
55 /**
56 * CurlClient constructor.
57 *
58 * Pass in a callable to $defaultOptions that returns an array of CURLOPT_* values to start
59 * off a request with, or an flat array with the same format used by curl_setopt_array() to
60 * provide a static set of options. Note that many options are overridden later in the request
61 * call, including timeouts, which can be set via setTimeout() and setConnectTimeout().
62 *
63 * Note that request() will silently ignore a non-callable, non-array $defaultOptions, and will
64 * throw an exception if $defaultOptions returns a non-array value.
65 *
66 * @param null|array|callable $defaultOptions
67 * @param null|\Stripe\Util\RandomGenerator $randomGenerator
68 */
69 public function __construct($defaultOptions = null, $randomGenerator = null)
70 {
71 $this->defaultOptions = $defaultOptions;
72 $this->randomGenerator = $randomGenerator ?: new Util\RandomGenerator();
73 $this->initUserAgentInfo();
74
75 $this->enableHttp2 = $this->canSafelyUseHttp2();
76 }
77
78 public function __destruct()
79 {
80 $this->closeCurlHandle();
81 }
82
83 public function initUserAgentInfo()
84 {
85 $curlVersion = \curl_version();
86 $this->userAgentInfo = [
87 'httplib' => 'curl ' . $curlVersion['version'],
88 'ssllib' => $curlVersion['ssl_version'],
89 ];
90 }
91
92 public function getDefaultOptions()
93 {
94 return $this->defaultOptions;
95 }
96
97 public function getUserAgentInfo()
98 {
99 return $this->userAgentInfo;
100 }
101
102 /**
103 * @return bool
104 */
105 public function getEnablePersistentConnections()
106 {
107 return $this->enablePersistentConnections;
108 }
109
110 /**
111 * @param bool $enable
112 */
113 public function setEnablePersistentConnections($enable)
114 {
115 $this->enablePersistentConnections = $enable;
116 }
117
118 /**
119 * @return bool
120 */
121 public function getEnableHttp2()
122 {
123 return $this->enableHttp2;
124 }
125
126 /**
127 * @param bool $enable
128 */
129 public function setEnableHttp2($enable)
130 {
131 $this->enableHttp2 = $enable;
132 }
133
134 /**
135 * @return null|callable
136 */
137 public function getRequestStatusCallback()
138 {
139 return $this->requestStatusCallback;
140 }
141
142 /**
143 * Sets a callback that is called after each request. The callback will
144 * receive the following parameters:
145 * <ol>
146 * <li>string $rbody The response body</li>
147 * <li>integer $rcode The response status code</li>
148 * <li>\Stripe\Util\CaseInsensitiveArray $rheaders The response headers</li>
149 * <li>integer $errno The curl error number</li>
150 * <li>string|null $message The curl error message</li>
151 * <li>boolean $shouldRetry Whether the request will be retried</li>
152 * <li>integer $numRetries The number of the retry attempt</li>
153 * </ol>.
154 *
155 * @param null|callable $requestStatusCallback
156 */
157 public function setRequestStatusCallback($requestStatusCallback)
158 {
159 $this->requestStatusCallback = $requestStatusCallback;
160 }
161
162 // USER DEFINED TIMEOUTS
163
164 const DEFAULT_TIMEOUT = 80;
165 const DEFAULT_CONNECT_TIMEOUT = 30;
166
167 private $timeout = self::DEFAULT_TIMEOUT;
168 private $connectTimeout = self::DEFAULT_CONNECT_TIMEOUT;
169
170 public function setTimeout($seconds)
171 {
172 $this->timeout = (int) \max($seconds, 0);
173
174 return $this;
175 }
176
177 public function setConnectTimeout($seconds)
178 {
179 $this->connectTimeout = (int) \max($seconds, 0);
180
181 return $this;
182 }
183
184 public function getTimeout()
185 {
186 return $this->timeout;
187 }
188
189 public function getConnectTimeout()
190 {
191 return $this->connectTimeout;
192 }
193
194 // END OF USER DEFINED TIMEOUTS
195
196 private function constructRequest($method, $absUrl, $headers, $params, $hasFile)
197 {
198 $method = \strtolower($method);
199
200 $opts = [];
201 if (\is_callable($this->defaultOptions)) { // call defaultOptions callback, set options to return value
202 $opts = \call_user_func_array($this->defaultOptions, \func_get_args());
203 if (!\is_array($opts)) {
204 throw new Exception\UnexpectedValueException('Non-array value returned by defaultOptions CurlClient callback');
205 }
206 } elseif (\is_array($this->defaultOptions)) { // set default curlopts from array
207 $opts = $this->defaultOptions;
208 }
209
210 $params = Util\Util::objectsToIds($params);
211
212 if ('get' === $method) {
213 if ($hasFile) {
214 throw new Exception\UnexpectedValueException(
215 'Issuing a GET request with a file parameter'
216 );
217 }
218 $opts[\CURLOPT_HTTPGET] = 1;
219 if (\count($params) > 0) {
220 $encoded = Util\Util::encodeParameters($params);
221 $absUrl = "{$absUrl}?{$encoded}";
222 }
223 } elseif ('post' === $method) {
224 $opts[\CURLOPT_POST] = 1;
225 $opts[\CURLOPT_POSTFIELDS] = $hasFile ? $params : Util\Util::encodeParameters($params);
226 } elseif ('delete' === $method) {
227 $opts[\CURLOPT_CUSTOMREQUEST] = 'DELETE';
228 if (\count($params) > 0) {
229 $encoded = Util\Util::encodeParameters($params);
230 $absUrl = "{$absUrl}?{$encoded}";
231 }
232 } else {
233 throw new Exception\UnexpectedValueException("Unrecognized method {$method}");
234 }
235
236 // It is only safe to retry network failures on POST requests if we
237 // add an Idempotency-Key header
238 if (('post' === $method) && (Stripe::$maxNetworkRetries > 0)) {
239 if (!$this->hasHeader($headers, 'Idempotency-Key')) {
240 $headers[] = 'Idempotency-Key: ' . $this->randomGenerator->uuid();
241 }
242 }
243
244 // By default for large request body sizes (> 1024 bytes), cURL will
245 // send a request without a body and with a `Expect: 100-continue`
246 // header, which gives the server a chance to respond with an error
247 // status code in cases where one can be determined right away (say
248 // on an authentication problem for example), and saves the "large"
249 // request body from being ever sent.
250 //
251 // Unfortunately, the bindings don't currently correctly handle the
252 // success case (in which the server sends back a 100 CONTINUE), so
253 // we'll error under that condition. To compensate for that problem
254 // for the time being, override cURL's behavior by simply always
255 // sending an empty `Expect:` header.
256 $headers[] = 'Expect: ';
257
258 $absUrl = Util\Util::utf8($absUrl);
259 $opts[\CURLOPT_URL] = $absUrl;
260 $opts[\CURLOPT_RETURNTRANSFER] = true;
261 $opts[\CURLOPT_CONNECTTIMEOUT] = $this->connectTimeout;
262 $opts[\CURLOPT_TIMEOUT] = $this->timeout;
263 $opts[\CURLOPT_HTTPHEADER] = $headers;
264 $opts[\CURLOPT_CAINFO] = Stripe::getCABundlePath();
265 if (!Stripe::getVerifySslCerts()) {
266 $opts[\CURLOPT_SSL_VERIFYPEER] = false;
267 }
268
269 if (!isset($opts[\CURLOPT_HTTP_VERSION]) && $this->getEnableHttp2()) {
270 // For HTTPS requests, enable HTTP/2, if supported
271 $opts[\CURLOPT_HTTP_VERSION] = \CURL_HTTP_VERSION_2TLS;
272 }
273
274 // If the user didn't explicitly specify a CURLOPT_IPRESOLVE option, we
275 // force IPv4 resolving as Stripe's API servers are only accessible over
276 // IPv4 (see. https://github.com/stripe/stripe-php/issues/1045).
277 // We let users specify a custom option in case they need to say proxy
278 // through an IPv6 proxy.
279 if (!isset($opts[\CURLOPT_IPRESOLVE])) {
280 $opts[\CURLOPT_IPRESOLVE] = \CURL_IPRESOLVE_V4;
281 }
282
283 return [$opts, $absUrl];
284 }
285
286 public function request($method, $absUrl, $headers, $params, $hasFile)
287 {
288 list($opts, $absUrl) = $this->constructRequest($method, $absUrl, $headers, $params, $hasFile);
289
290 list($rbody, $rcode, $rheaders) = $this->executeRequestWithRetries($opts, $absUrl);
291
292 return [$rbody, $rcode, $rheaders];
293 }
294
295 public function requestStream($method, $absUrl, $headers, $params, $hasFile, $readBodyChunk)
296 {
297 list($opts, $absUrl) = $this->constructRequest($method, $absUrl, $headers, $params, $hasFile);
298
299 $opts[\CURLOPT_RETURNTRANSFER] = false;
300 list($rbody, $rcode, $rheaders) = $this->executeStreamingRequestWithRetries($opts, $absUrl, $readBodyChunk);
301
302 return [$rbody, $rcode, $rheaders];
303 }
304
305 /**
306 * Curl permits sending \CURLOPT_HEADERFUNCTION, which is called with lines
307 * from the header and \CURLOPT_WRITEFUNCTION, which is called with bytes
308 * from the body. You usually want to handle the body differently depending
309 * on what was in the header.
310 *
311 * This function makes it easier to specify different callbacks depending
312 * on the contents of the heeder. After the header has been completely read
313 * and the body begins to stream, it will call $determineWriteCallback with
314 * the array of headers. $determineWriteCallback should, based on the
315 * headers it receives, return a "writeCallback" that describes what to do
316 * with the incoming HTTP response body.
317 *
318 * @param array $opts
319 * @param callable $determineWriteCallback
320 *
321 * @return array
322 */
323 private function useHeadersToDetermineWriteCallback($opts, $determineWriteCallback)
324 {
325 $rheaders = new Util\CaseInsensitiveArray();
326 $headerCallback = function ($curl, $header_line) use (&$rheaders) {
327 return self::parseLineIntoHeaderArray($header_line, $rheaders);
328 };
329
330 $writeCallback = null;
331 $writeCallbackWrapper = function ($curl, $data) use (&$writeCallback, &$rheaders, &$determineWriteCallback) {
332 if (null === $writeCallback) {
333 $writeCallback = \call_user_func_array($determineWriteCallback, [$rheaders]);
334 }
335
336 return \call_user_func_array($writeCallback, [$curl, $data]);
337 };
338
339 return [$headerCallback, $writeCallbackWrapper];
340 }
341
342 private static function parseLineIntoHeaderArray($line, &$headers)
343 {
344 if (false === \strpos($line, ':')) {
345 return \strlen($line);
346 }
347 list($key, $value) = \explode(':', \trim($line), 2);
348 $headers[\trim($key)] = \trim($value);
349
350 return \strlen($line);
351 }
352
353 /**
354 * Like `executeRequestWithRetries` except:
355 * 1. Does not buffer the body of a successful (status code < 300)
356 * response into memory -- instead, calls the caller-provided
357 * $readBodyChunk with each chunk of incoming data.
358 * 2. Does not retry if a network error occurs while streaming the
359 * body of a successful response.
360 *
361 * @param array $opts cURL options
362 * @param string $absUrl
363 * @param callable $readBodyChunk
364 *
365 * @return array
366 */
367 public function executeStreamingRequestWithRetries($opts, $absUrl, $readBodyChunk)
368 {
369 /** @var bool */
370 $shouldRetry = false;
371 /** @var int */
372 $numRetries = 0;
373
374 // Will contain the bytes of the body of the last request
375 // if it was not successful and should not be retries
376 /** @var null|string */
377 $rbody = null;
378
379 // Status code of the last request
380 /** @var null|bool */
381 $rcode = null;
382
383 // Array of headers from the last request
384 /** @var null|array */
385 $lastRHeaders = null;
386
387 $errno = null;
388 $message = null;
389
390 $determineWriteCallback = function ($rheaders) use (
391 &$readBodyChunk,
392 &$shouldRetry,
393 &$rbody,
394 &$numRetries,
395 &$rcode,
396 &$lastRHeaders,
397 &$errno
398 ) {
399 $lastRHeaders = $rheaders;
400 $errno = \curl_errno($this->curlHandle);
401
402 $rcode = \curl_getinfo($this->curlHandle, \CURLINFO_HTTP_CODE);
403
404 // Send the bytes from the body of a successful request to the caller-provided $readBodyChunk.
405 if ($rcode < 300) {
406 $rbody = null;
407
408 return function ($curl, $data) use (&$readBodyChunk) {
409 // Don't expose the $curl handle to the user, and don't require them to
410 // return the length of $data.
411 \call_user_func_array($readBodyChunk, [$data]);
412
413 return \strlen($data);
414 };
415 }
416
417 $shouldRetry = $this->shouldRetry($errno, $rcode, $rheaders, $numRetries);
418
419 // Discard the body from an unsuccessful request that should be retried.
420 if ($shouldRetry) {
421 return function ($curl, $data) {
422 return \strlen($data);
423 };
424 } else {
425 // Otherwise, buffer the body into $rbody. It will need to be parsed to determine
426 // which exception to throw to the user.
427 $rbody = '';
428
429 return function ($curl, $data) use (&$rbody) {
430 $rbody .= $data;
431
432 return \strlen($data);
433 };
434 }
435 };
436
437 while (true) {
438 list($headerCallback, $writeCallback) = $this->useHeadersToDetermineWriteCallback($opts, $determineWriteCallback);
439 $opts[\CURLOPT_HEADERFUNCTION] = $headerCallback;
440 $opts[\CURLOPT_WRITEFUNCTION] = $writeCallback;
441
442 $shouldRetry = false;
443 $rbody = null;
444 $this->resetCurlHandle();
445 \curl_setopt_array($this->curlHandle, $opts);
446 $result = \curl_exec($this->curlHandle);
447 $errno = \curl_errno($this->curlHandle);
448 if (0 !== $errno) {
449 $message = \curl_error($this->curlHandle);
450 }
451 if (!$this->getEnablePersistentConnections()) {
452 $this->closeCurlHandle();
453 }
454
455 if (\is_callable($this->getRequestStatusCallback())) {
456 \call_user_func_array(
457 $this->getRequestStatusCallback(),
458 [$rbody, $rcode, $lastRHeaders, $errno, $message, $shouldRetry, $numRetries]
459 );
460 }
461
462 if ($shouldRetry) {
463 ++$numRetries;
464 $sleepSeconds = $this->sleepTime($numRetries, $lastRHeaders);
465 \usleep((int) ($sleepSeconds * 1000000));
466 } else {
467 break;
468 }
469 }
470
471 if (0 !== $errno) {
472 $this->handleCurlError($absUrl, $errno, $message, $numRetries);
473 }
474
475 return [$rbody, $rcode, $lastRHeaders];
476 }
477
478 /**
479 * @param array $opts cURL options
480 * @param string $absUrl
481 */
482 public function executeRequestWithRetries($opts, $absUrl)
483 {
484 $numRetries = 0;
485
486 while (true) {
487 $rcode = 0;
488 $errno = 0;
489 $message = null;
490
491 // Create a callback to capture HTTP headers for the response
492 $rheaders = new Util\CaseInsensitiveArray();
493 $headerCallback = function ($curl, $header_line) use (&$rheaders) {
494 return CurlClient::parseLineIntoHeaderArray($header_line, $rheaders);
495 };
496 $opts[\CURLOPT_HEADERFUNCTION] = $headerCallback;
497
498 $this->resetCurlHandle();
499 \curl_setopt_array($this->curlHandle, $opts);
500 $rbody = \curl_exec($this->curlHandle);
501
502 if (false === $rbody) {
503 $errno = \curl_errno($this->curlHandle);
504 $message = \curl_error($this->curlHandle);
505 } else {
506 $rcode = \curl_getinfo($this->curlHandle, \CURLINFO_HTTP_CODE);
507 }
508 if (!$this->getEnablePersistentConnections()) {
509 $this->closeCurlHandle();
510 }
511
512 $shouldRetry = $this->shouldRetry($errno, $rcode, $rheaders, $numRetries);
513
514 if (\is_callable($this->getRequestStatusCallback())) {
515 \call_user_func_array(
516 $this->getRequestStatusCallback(),
517 [$rbody, $rcode, $rheaders, $errno, $message, $shouldRetry, $numRetries]
518 );
519 }
520
521 if ($shouldRetry) {
522 ++$numRetries;
523 $sleepSeconds = $this->sleepTime($numRetries, $rheaders);
524 \usleep((int) ($sleepSeconds * 1000000));
525 } else {
526 break;
527 }
528 }
529
530 if (false === $rbody) {
531 $this->handleCurlError($absUrl, $errno, $message, $numRetries);
532 }
533
534 return [$rbody, $rcode, $rheaders];
535 }
536
537 /**
538 * @param string $url
539 * @param int $errno
540 * @param string $message
541 * @param int $numRetries
542 *
543 * @throws Exception\ApiConnectionException
544 */
545 private function handleCurlError($url, $errno, $message, $numRetries)
546 {
547 switch ($errno) {
548 case \CURLE_COULDNT_CONNECT:
549 case \CURLE_COULDNT_RESOLVE_HOST:
550 case \CURLE_OPERATION_TIMEOUTED:
551 $msg = "Could not connect to Stripe ({$url}). Please check your "
552 . 'internet connection and try again. If this problem persists, '
553 . "you should check Stripe's service status at "
554 . 'https://twitter.com/stripestatus, or';
555
556 break;
557
558 case \CURLE_SSL_CACERT:
559 case \CURLE_SSL_PEER_CERTIFICATE:
560 $msg = "Could not verify Stripe's SSL certificate. Please make sure "
561 . 'that your network is not intercepting certificates. '
562 . "(Try going to {$url} in your browser.) "
563 . 'If this problem persists,';
564
565 break;
566
567 default:
568 $msg = 'Unexpected error communicating with Stripe. '
569 . 'If this problem persists,';
570 }
571 $msg .= ' let us know at support@stripe.com.';
572
573 $msg .= "\n\n(Network error [errno {$errno}]: {$message})";
574
575 if ($numRetries > 0) {
576 $msg .= "\n\nRequest was retried {$numRetries} times.";
577 }
578
579 throw new Exception\ApiConnectionException($msg);
580 }
581
582 /**
583 * Checks if an error is a problem that we should retry on. This includes both
584 * socket errors that may represent an intermittent problem and some special
585 * HTTP statuses.
586 *
587 * @param int $errno
588 * @param int $rcode
589 * @param array|\Stripe\Util\CaseInsensitiveArray $rheaders
590 * @param int $numRetries
591 *
592 * @return bool
593 */
594 private function shouldRetry($errno, $rcode, $rheaders, $numRetries)
595 {
596 if ($numRetries >= Stripe::getMaxNetworkRetries()) {
597 return false;
598 }
599
600 // Retry on timeout-related problems (either on open or read).
601 if (\CURLE_OPERATION_TIMEOUTED === $errno) {
602 return true;
603 }
604
605 // Destination refused the connection, the connection was reset, or a
606 // variety of other connection failures. This could occur from a single
607 // saturated server, so retry in case it's intermittent.
608 if (\CURLE_COULDNT_CONNECT === $errno) {
609 return true;
610 }
611
612 // The API may ask us not to retry (eg; if doing so would be a no-op)
613 // or advise us to retry (eg; in cases of lock timeouts); we defer to that.
614 if (isset($rheaders['stripe-should-retry'])) {
615 if ('false' === $rheaders['stripe-should-retry']) {
616 return false;
617 }
618 if ('true' === $rheaders['stripe-should-retry']) {
619 return true;
620 }
621 }
622
623 // 409 Conflict
624 if (409 === $rcode) {
625 return true;
626 }
627
628 // Retry on 500, 503, and other internal errors.
629 //
630 // Note that we expect the stripe-should-retry header to be false
631 // in most cases when a 500 is returned, since our idempotency framework
632 // would typically replay it anyway.
633 if ($rcode >= 500) {
634 return true;
635 }
636
637 return false;
638 }
639
640 /**
641 * Provides the number of seconds to wait before retrying a request.
642 *
643 * @param int $numRetries
644 * @param array|\Stripe\Util\CaseInsensitiveArray $rheaders
645 *
646 * @return int
647 */
648 private function sleepTime($numRetries, $rheaders)
649 {
650 // Apply exponential backoff with $initialNetworkRetryDelay on the
651 // number of $numRetries so far as inputs. Do not allow the number to exceed
652 // $maxNetworkRetryDelay.
653 $sleepSeconds = \min(
654 Stripe::getInitialNetworkRetryDelay() * 1.0 * 2 ** ($numRetries - 1),
655 Stripe::getMaxNetworkRetryDelay()
656 );
657
658 // Apply some jitter by randomizing the value in the range of
659 // ($sleepSeconds / 2) to ($sleepSeconds).
660 $sleepSeconds *= 0.5 * (1 + $this->randomGenerator->randFloat());
661
662 // But never sleep less than the base sleep seconds.
663 $sleepSeconds = \max(Stripe::getInitialNetworkRetryDelay(), $sleepSeconds);
664
665 // And never sleep less than the time the API asks us to wait, assuming it's a reasonable ask.
666 $retryAfter = isset($rheaders['retry-after']) ? (float) ($rheaders['retry-after']) : 0.0;
667 if (\floor($retryAfter) === $retryAfter && $retryAfter <= Stripe::getMaxRetryAfter()) {
668 $sleepSeconds = \max($sleepSeconds, $retryAfter);
669 }
670
671 return $sleepSeconds;
672 }
673
674 /**
675 * Initializes the curl handle. If already initialized, the handle is closed first.
676 */
677 private function initCurlHandle()
678 {
679 $this->closeCurlHandle();
680 $this->curlHandle = \curl_init();
681 }
682
683 /**
684 * Closes the curl handle if initialized. Do nothing if already closed.
685 */
686 private function closeCurlHandle()
687 {
688 if (null !== $this->curlHandle) {
689 \curl_close($this->curlHandle);
690 $this->curlHandle = null;
691 }
692 }
693
694 /**
695 * Resets the curl handle. If the handle is not already initialized, or if persistent
696 * connections are disabled, the handle is reinitialized instead.
697 */
698 private function resetCurlHandle()
699 {
700 if (null !== $this->curlHandle && $this->getEnablePersistentConnections()) {
701 \curl_reset($this->curlHandle);
702 } else {
703 $this->initCurlHandle();
704 }
705 }
706
707 /**
708 * Indicates whether it is safe to use HTTP/2 or not.
709 *
710 * @return bool
711 */
712 private function canSafelyUseHttp2()
713 {
714 // Versions of curl older than 7.60.0 don't respect GOAWAY frames
715 // (cf. https://github.com/curl/curl/issues/2416), which Stripe use.
716 $curlVersion = \curl_version()['version'];
717
718 return \version_compare($curlVersion, '7.60.0') >= 0;
719 }
720
721 /**
722 * Checks if a list of headers contains a specific header name.
723 *
724 * @param string[] $headers
725 * @param string $name
726 *
727 * @return bool
728 */
729 private function hasHeader($headers, $name)
730 {
731 foreach ($headers as $header) {
732 if (0 === \strncasecmp($header, "{$name}: ", \strlen($name) + 2)) {
733 return true;
734 }
735 }
736
737 return false;
738 }
739 }
740