PluginProbe ʕ •ᴥ•ʔ
GiveWP – Donation Plugin and Fundraising Platform / 2.24.0
GiveWP – Donation Plugin and Fundraising Platform v2.24.0
4.16.4 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 4 years ago StreamingClientInterface.php 4 years ago
CurlClient.php
735 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 // Stripe's API servers are only accessible over IPv4. Force IPv4 resolving to avoid
275 // potential issues (cf. https://github.com/stripe/stripe-php/issues/1045).
276 $opts[\CURLOPT_IPRESOLVE] = \CURL_IPRESOLVE_V4;
277
278 return [$opts, $absUrl];
279 }
280
281 public function request($method, $absUrl, $headers, $params, $hasFile)
282 {
283 list($opts, $absUrl) = $this->constructRequest($method, $absUrl, $headers, $params, $hasFile);
284
285 list($rbody, $rcode, $rheaders) = $this->executeRequestWithRetries($opts, $absUrl);
286
287 return [$rbody, $rcode, $rheaders];
288 }
289
290 public function requestStream($method, $absUrl, $headers, $params, $hasFile, $readBodyChunk)
291 {
292 list($opts, $absUrl) = $this->constructRequest($method, $absUrl, $headers, $params, $hasFile);
293
294 $opts[\CURLOPT_RETURNTRANSFER] = false;
295 list($rbody, $rcode, $rheaders) = $this->executeStreamingRequestWithRetries($opts, $absUrl, $readBodyChunk);
296
297 return [$rbody, $rcode, $rheaders];
298 }
299
300 /**
301 * Curl permits sending \CURLOPT_HEADERFUNCTION, which is called with lines
302 * from the header and \CURLOPT_WRITEFUNCTION, which is called with bytes
303 * from the body. You usually want to handle the body differently depending
304 * on what was in the header.
305 *
306 * This function makes it easier to specify different callbacks depending
307 * on the contents of the heeder. After the header has been completely read
308 * and the body begins to stream, it will call $determineWriteCallback with
309 * the array of headers. $determineWriteCallback should, based on the
310 * headers it receives, return a "writeCallback" that describes what to do
311 * with the incoming HTTP response body.
312 *
313 * @param array $opts
314 * @param callable $determineWriteCallback
315 *
316 * @return array
317 */
318 private function useHeadersToDetermineWriteCallback($opts, $determineWriteCallback)
319 {
320 $rheaders = new Util\CaseInsensitiveArray();
321 $headerCallback = function ($curl, $header_line) use (&$rheaders) {
322 return self::parseLineIntoHeaderArray($header_line, $rheaders);
323 };
324
325 $writeCallback = null;
326 $writeCallbackWrapper = function ($curl, $data) use (&$writeCallback, &$rheaders, &$determineWriteCallback) {
327 if (null === $writeCallback) {
328 $writeCallback = \call_user_func_array($determineWriteCallback, [$rheaders]);
329 }
330
331 return \call_user_func_array($writeCallback, [$curl, $data]);
332 };
333
334 return [$headerCallback, $writeCallbackWrapper];
335 }
336
337 private static function parseLineIntoHeaderArray($line, &$headers)
338 {
339 if (false === \strpos($line, ':')) {
340 return \strlen($line);
341 }
342 list($key, $value) = \explode(':', \trim($line), 2);
343 $headers[\trim($key)] = \trim($value);
344
345 return \strlen($line);
346 }
347
348 /**
349 * Like `executeRequestWithRetries` except:
350 * 1. Does not buffer the body of a successful (status code < 300)
351 * response into memory -- instead, calls the caller-provided
352 * $readBodyChunk with each chunk of incoming data.
353 * 2. Does not retry if a network error occurs while streaming the
354 * body of a successful response.
355 *
356 * @param array $opts cURL options
357 * @param string $absUrl
358 * @param callable $readBodyChunk
359 *
360 * @return array
361 */
362 public function executeStreamingRequestWithRetries($opts, $absUrl, $readBodyChunk)
363 {
364 /** @var bool */
365 $shouldRetry = false;
366 /** @var int */
367 $numRetries = 0;
368
369 // Will contain the bytes of the body of the last request
370 // if it was not successful and should not be retries
371 /** @var null|string */
372 $rbody = null;
373
374 // Status code of the last request
375 /** @var null|bool */
376 $rcode = null;
377
378 // Array of headers from the last request
379 /** @var null|array */
380 $lastRHeaders = null;
381
382 $errno = null;
383 $message = null;
384
385 $determineWriteCallback = function ($rheaders) use (
386 &$readBodyChunk,
387 &$shouldRetry,
388 &$rbody,
389 &$numRetries,
390 &$rcode,
391 &$lastRHeaders,
392 &$errno
393 ) {
394 $lastRHeaders = $rheaders;
395 $errno = \curl_errno($this->curlHandle);
396
397 $rcode = \curl_getinfo($this->curlHandle, \CURLINFO_HTTP_CODE);
398
399 // Send the bytes from the body of a successful request to the caller-provided $readBodyChunk.
400 if ($rcode < 300) {
401 $rbody = null;
402
403 return function ($curl, $data) use (&$readBodyChunk) {
404 // Don't expose the $curl handle to the user, and don't require them to
405 // return the length of $data.
406 \call_user_func_array($readBodyChunk, [$data]);
407
408 return \strlen($data);
409 };
410 }
411
412 $shouldRetry = $this->shouldRetry($errno, $rcode, $rheaders, $numRetries);
413
414 // Discard the body from an unsuccessful request that should be retried.
415 if ($shouldRetry) {
416 return function ($curl, $data) {
417 return \strlen($data);
418 };
419 } else {
420 // Otherwise, buffer the body into $rbody. It will need to be parsed to determine
421 // which exception to throw to the user.
422 $rbody = '';
423
424 return function ($curl, $data) use (&$rbody) {
425 $rbody .= $data;
426
427 return \strlen($data);
428 };
429 }
430 };
431
432 while (true) {
433 list($headerCallback, $writeCallback) = $this->useHeadersToDetermineWriteCallback($opts, $determineWriteCallback);
434 $opts[\CURLOPT_HEADERFUNCTION] = $headerCallback;
435 $opts[\CURLOPT_WRITEFUNCTION] = $writeCallback;
436
437 $shouldRetry = false;
438 $rbody = null;
439 $this->resetCurlHandle();
440 \curl_setopt_array($this->curlHandle, $opts);
441 $result = \curl_exec($this->curlHandle);
442 $errno = \curl_errno($this->curlHandle);
443 if (0 !== $errno) {
444 $message = \curl_error($this->curlHandle);
445 }
446 if (!$this->getEnablePersistentConnections()) {
447 $this->closeCurlHandle();
448 }
449
450 if (\is_callable($this->getRequestStatusCallback())) {
451 \call_user_func_array(
452 $this->getRequestStatusCallback(),
453 [$rbody, $rcode, $lastRHeaders, $errno, $message, $shouldRetry, $numRetries]
454 );
455 }
456
457 if ($shouldRetry) {
458 ++$numRetries;
459 $sleepSeconds = $this->sleepTime($numRetries, $lastRHeaders);
460 \usleep((int) ($sleepSeconds * 1000000));
461 } else {
462 break;
463 }
464 }
465
466 if (0 !== $errno) {
467 $this->handleCurlError($absUrl, $errno, $message, $numRetries);
468 }
469
470 return [$rbody, $rcode, $lastRHeaders];
471 }
472
473 /**
474 * @param array $opts cURL options
475 * @param string $absUrl
476 */
477 public function executeRequestWithRetries($opts, $absUrl)
478 {
479 $numRetries = 0;
480
481 while (true) {
482 $rcode = 0;
483 $errno = 0;
484 $message = null;
485
486 // Create a callback to capture HTTP headers for the response
487 $rheaders = new Util\CaseInsensitiveArray();
488 $headerCallback = function ($curl, $header_line) use (&$rheaders) {
489 return CurlClient::parseLineIntoHeaderArray($header_line, $rheaders);
490 };
491 $opts[\CURLOPT_HEADERFUNCTION] = $headerCallback;
492
493 $this->resetCurlHandle();
494 \curl_setopt_array($this->curlHandle, $opts);
495 $rbody = \curl_exec($this->curlHandle);
496
497 if (false === $rbody) {
498 $errno = \curl_errno($this->curlHandle);
499 $message = \curl_error($this->curlHandle);
500 } else {
501 $rcode = \curl_getinfo($this->curlHandle, \CURLINFO_HTTP_CODE);
502 }
503 if (!$this->getEnablePersistentConnections()) {
504 $this->closeCurlHandle();
505 }
506
507 $shouldRetry = $this->shouldRetry($errno, $rcode, $rheaders, $numRetries);
508
509 if (\is_callable($this->getRequestStatusCallback())) {
510 \call_user_func_array(
511 $this->getRequestStatusCallback(),
512 [$rbody, $rcode, $rheaders, $errno, $message, $shouldRetry, $numRetries]
513 );
514 }
515
516 if ($shouldRetry) {
517 ++$numRetries;
518 $sleepSeconds = $this->sleepTime($numRetries, $rheaders);
519 \usleep((int) ($sleepSeconds * 1000000));
520 } else {
521 break;
522 }
523 }
524
525 if (false === $rbody) {
526 $this->handleCurlError($absUrl, $errno, $message, $numRetries);
527 }
528
529 return [$rbody, $rcode, $rheaders];
530 }
531
532 /**
533 * @param string $url
534 * @param int $errno
535 * @param string $message
536 * @param int $numRetries
537 *
538 * @throws Exception\ApiConnectionException
539 */
540 private function handleCurlError($url, $errno, $message, $numRetries)
541 {
542 switch ($errno) {
543 case \CURLE_COULDNT_CONNECT:
544 case \CURLE_COULDNT_RESOLVE_HOST:
545 case \CURLE_OPERATION_TIMEOUTED:
546 $msg = "Could not connect to Stripe ({$url}). Please check your "
547 . 'internet connection and try again. If this problem persists, '
548 . "you should check Stripe's service status at "
549 . 'https://twitter.com/stripestatus, or';
550
551 break;
552
553 case \CURLE_SSL_CACERT:
554 case \CURLE_SSL_PEER_CERTIFICATE:
555 $msg = "Could not verify Stripe's SSL certificate. Please make sure "
556 . 'that your network is not intercepting certificates. '
557 . "(Try going to {$url} in your browser.) "
558 . 'If this problem persists,';
559
560 break;
561
562 default:
563 $msg = 'Unexpected error communicating with Stripe. '
564 . 'If this problem persists,';
565 }
566 $msg .= ' let us know at support@stripe.com.';
567
568 $msg .= "\n\n(Network error [errno {$errno}]: {$message})";
569
570 if ($numRetries > 0) {
571 $msg .= "\n\nRequest was retried {$numRetries} times.";
572 }
573
574 throw new Exception\ApiConnectionException($msg);
575 }
576
577 /**
578 * Checks if an error is a problem that we should retry on. This includes both
579 * socket errors that may represent an intermittent problem and some special
580 * HTTP statuses.
581 *
582 * @param int $errno
583 * @param int $rcode
584 * @param array|\Stripe\Util\CaseInsensitiveArray $rheaders
585 * @param int $numRetries
586 *
587 * @return bool
588 */
589 private function shouldRetry($errno, $rcode, $rheaders, $numRetries)
590 {
591 if ($numRetries >= Stripe::getMaxNetworkRetries()) {
592 return false;
593 }
594
595 // Retry on timeout-related problems (either on open or read).
596 if (\CURLE_OPERATION_TIMEOUTED === $errno) {
597 return true;
598 }
599
600 // Destination refused the connection, the connection was reset, or a
601 // variety of other connection failures. This could occur from a single
602 // saturated server, so retry in case it's intermittent.
603 if (\CURLE_COULDNT_CONNECT === $errno) {
604 return true;
605 }
606
607 // The API may ask us not to retry (eg; if doing so would be a no-op)
608 // or advise us to retry (eg; in cases of lock timeouts); we defer to that.
609 if (isset($rheaders['stripe-should-retry'])) {
610 if ('false' === $rheaders['stripe-should-retry']) {
611 return false;
612 }
613 if ('true' === $rheaders['stripe-should-retry']) {
614 return true;
615 }
616 }
617
618 // 409 Conflict
619 if (409 === $rcode) {
620 return true;
621 }
622
623 // Retry on 500, 503, and other internal errors.
624 //
625 // Note that we expect the stripe-should-retry header to be false
626 // in most cases when a 500 is returned, since our idempotency framework
627 // would typically replay it anyway.
628 if ($rcode >= 500) {
629 return true;
630 }
631
632 return false;
633 }
634
635 /**
636 * Provides the number of seconds to wait before retrying a request.
637 *
638 * @param int $numRetries
639 * @param array|\Stripe\Util\CaseInsensitiveArray $rheaders
640 *
641 * @return int
642 */
643 private function sleepTime($numRetries, $rheaders)
644 {
645 // Apply exponential backoff with $initialNetworkRetryDelay on the
646 // number of $numRetries so far as inputs. Do not allow the number to exceed
647 // $maxNetworkRetryDelay.
648 $sleepSeconds = \min(
649 Stripe::getInitialNetworkRetryDelay() * 1.0 * 2 ** ($numRetries - 1),
650 Stripe::getMaxNetworkRetryDelay()
651 );
652
653 // Apply some jitter by randomizing the value in the range of
654 // ($sleepSeconds / 2) to ($sleepSeconds).
655 $sleepSeconds *= 0.5 * (1 + $this->randomGenerator->randFloat());
656
657 // But never sleep less than the base sleep seconds.
658 $sleepSeconds = \max(Stripe::getInitialNetworkRetryDelay(), $sleepSeconds);
659
660 // And never sleep less than the time the API asks us to wait, assuming it's a reasonable ask.
661 $retryAfter = isset($rheaders['retry-after']) ? (float) ($rheaders['retry-after']) : 0.0;
662 if (\floor($retryAfter) === $retryAfter && $retryAfter <= Stripe::getMaxRetryAfter()) {
663 $sleepSeconds = \max($sleepSeconds, $retryAfter);
664 }
665
666 return $sleepSeconds;
667 }
668
669 /**
670 * Initializes the curl handle. If already initialized, the handle is closed first.
671 */
672 private function initCurlHandle()
673 {
674 $this->closeCurlHandle();
675 $this->curlHandle = \curl_init();
676 }
677
678 /**
679 * Closes the curl handle if initialized. Do nothing if already closed.
680 */
681 private function closeCurlHandle()
682 {
683 if (null !== $this->curlHandle) {
684 \curl_close($this->curlHandle);
685 $this->curlHandle = null;
686 }
687 }
688
689 /**
690 * Resets the curl handle. If the handle is not already initialized, or if persistent
691 * connections are disabled, the handle is reinitialized instead.
692 */
693 private function resetCurlHandle()
694 {
695 if (null !== $this->curlHandle && $this->getEnablePersistentConnections()) {
696 \curl_reset($this->curlHandle);
697 } else {
698 $this->initCurlHandle();
699 }
700 }
701
702 /**
703 * Indicates whether it is safe to use HTTP/2 or not.
704 *
705 * @return bool
706 */
707 private function canSafelyUseHttp2()
708 {
709 // Versions of curl older than 7.60.0 don't respect GOAWAY frames
710 // (cf. https://github.com/curl/curl/issues/2416), which Stripe use.
711 $curlVersion = \curl_version()['version'];
712
713 return \version_compare($curlVersion, '7.60.0') >= 0;
714 }
715
716 /**
717 * Checks if a list of headers contains a specific header name.
718 *
719 * @param string[] $headers
720 * @param string $name
721 *
722 * @return bool
723 */
724 private function hasHeader($headers, $name)
725 {
726 foreach ($headers as $header) {
727 if (0 === \strncasecmp($header, "{$name}: ", \strlen($name) + 2)) {
728 return true;
729 }
730 }
731
732 return false;
733 }
734 }
735