PluginProbe ʕ •ᴥ•ʔ
Paid Membership Plugin, Ecommerce, User Registration Form, Login Form, User Profile & Restrict Content – ProfilePress / 4.17.1
Paid Membership Plugin, Ecommerce, User Registration Form, Login Form, User Profile & Restrict Content – ProfilePress v4.17.1
4.17.1 4.17.0 4.16.19 4.16.18 4.16.17 4.16.16 trunk 1.0 1.0.1 1.0.2 1.1 1.1.1 1.1.2 1.1.3 1.1.4 1.1.5 1.1.5a 1.1.6 1.1.7 1.2 1.2.1 1.2.2 1.2.3 1.2.4 1.2.5 1.2.6 1.3 1.3.1 1.3.2 1.3.3 1.3.4 1.3.5 1.3.6 1.4 1.4.1 1.4.2 1.5 1.5.1 1.5.2 1.5.3 1.5.4 1.5.5 1.5.6 1.5.7 1.5.8 1.6 1.6.1 1.6.2 1.6.3 1.6.4 1.6.5 1.6.6 1.6.7 1.6.8 1.7 1.7.1 1.7.2 1.8 1.8.1 1.8.10 1.8.2 1.8.3 1.8.4 1.8.5 1.8.6 1.8.7 1.8.8 1.8.9 1.9 1.9.1 1.9.10 1.9.11 1.9.12 1.9.13 1.9.2 1.9.3 1.9.4 1.9.5 1.9.6 1.9.7 1.9.8 1.9.9 2.1.9 2.2.10 2.2.11 2.2.12 2.2.13 2.2.14 2.2.15 2.2.16 2.2.2 2.2.5 2.2.6 2.2.7 2.2.8 2.2.9 3.0 3.1 3.1.1 3.1.10 3.1.11 3.1.12 3.1.13 3.1.14 3.1.15 3.1.16 3.1.17 3.1.18 3.1.19 3.1.2 3.1.3 3.1.4 3.1.5 3.1.6 3.1.7 3.1.8 3.1.9 3.2.0 3.2.1 3.2.10 3.2.11 3.2.12 3.2.13 3.2.14 3.2.15 3.2.16 3.2.2 3.2.3 3.2.4 3.2.5 3.2.6 3.2.7 3.2.8 3.2.9 4.0.0 4.0.1 4.0.2 4.0.3 4.1.0 4.1.1 4.1.2 4.1.3 4.1.4 4.10.0 4.10.1 4.10.2 4.10.3 4.11.0 4.12.0 4.13.0 4.13.1 4.13.2 4.13.3 4.13.4 4.14.0 4.14.1 4.14.2 4.14.3 4.14.4 4.15.0 4.15.1 4.15.10 4.15.11 4.15.12 4.15.13 4.15.14 4.15.15 4.15.16 4.15.17 4.15.18 4.15.19 4.15.2 4.15.20 4.15.20.1 4.15.21 4.15.22 4.15.23 4.15.24 4.15.25 4.15.3 4.15.4 4.15.5 4.15.6 4.15.7 4.15.8 4.15.9 4.16.0 4.16.1 4.16.10 4.16.11 4.16.12 4.16.13 4.16.14 4.16.15 4.16.2 4.16.3 4.16.4 4.16.5 4.16.6 4.16.7 4.16.8 4.16.9 4.2.0 4.3.0 4.3.1 4.3.2 4.4.0 4.4.1 4.5.0 4.5.1 4.5.2 4.5.3 4.5.4 4.5.5 4.6.0 4.7.0 4.8.0 4.9.0
wp-user-avatar / third-party / vendor / stripe / stripe-php / lib / HttpClient / CurlClient.php
wp-user-avatar / third-party / vendor / stripe / stripe-php / lib / HttpClient Last commit date
ClientInterface.php 2 months ago CurlClient.php 2 months ago StreamingClientInterface.php 2 months ago
CurlClient.php
657 lines
1 <?php
2
3 namespace ProfilePressVendor\Stripe\HttpClient;
4
5 use ProfilePressVendor\Stripe\Exception;
6 use ProfilePressVendor\Stripe\Stripe;
7 use ProfilePressVendor\Stripe\Util;
8 // @codingStandardsIgnoreStart
9 // PSR2 requires all constants be upper case. Sadly, the CURL_SSLVERSION
10 // constants do not abide by those rules.
11 // Note the values come from their position in the enums that
12 // defines them in cURL's source code.
13 // Available since PHP 5.5.19 and 5.6.3
14 if (!\defined('CURL_SSLVERSION_TLSv1_2')) {
15 \define('CURL_SSLVERSION_TLSv1_2', 6);
16 }
17 // @codingStandardsIgnoreEnd
18 // Available since PHP 7.0.7 and cURL 7.47.0
19 if (!\defined('CURL_HTTP_VERSION_2TLS')) {
20 \define('CURL_HTTP_VERSION_2TLS', 4);
21 }
22 class CurlClient implements ClientInterface, StreamingClientInterface
23 {
24 protected static $instance;
25 public static function instance()
26 {
27 if (!static::$instance) {
28 static::$instance = new static();
29 }
30 return static::$instance;
31 }
32 protected $defaultOptions;
33 /** @var \Stripe\Util\RandomGenerator */
34 protected $randomGenerator;
35 protected $userAgentInfo;
36 protected $enablePersistentConnections = \true;
37 protected $enableHttp2;
38 protected $curlHandle;
39 protected $requestStatusCallback;
40 /**
41 * CurlClient constructor.
42 *
43 * Pass in a callable to $defaultOptions that returns an array of CURLOPT_* values to start
44 * off a request with, or an flat array with the same format used by curl_setopt_array() to
45 * provide a static set of options. Note that many options are overridden later in the request
46 * call, including timeouts, which can be set via setTimeout() and setConnectTimeout().
47 *
48 * Note that request() will silently ignore a non-callable, non-array $defaultOptions, and will
49 * throw an exception if $defaultOptions returns a non-array value.
50 *
51 * @param null|array|callable $defaultOptions
52 * @param null|\Stripe\Util\RandomGenerator $randomGenerator
53 */
54 public function __construct($defaultOptions = null, $randomGenerator = null)
55 {
56 $this->defaultOptions = $defaultOptions;
57 $this->randomGenerator = $randomGenerator ?: new Util\RandomGenerator();
58 $this->initUserAgentInfo();
59 $this->enableHttp2 = $this->canSafelyUseHttp2();
60 }
61 public function __destruct()
62 {
63 $this->closeCurlHandle();
64 }
65 public function initUserAgentInfo()
66 {
67 $curlVersion = \curl_version();
68 $this->userAgentInfo = ['httplib' => 'curl ' . $curlVersion['version'], 'ssllib' => $curlVersion['ssl_version']];
69 }
70 public function getDefaultOptions()
71 {
72 return $this->defaultOptions;
73 }
74 public function getUserAgentInfo()
75 {
76 return $this->userAgentInfo;
77 }
78 /**
79 * @return bool
80 */
81 public function getEnablePersistentConnections()
82 {
83 return $this->enablePersistentConnections;
84 }
85 /**
86 * @param bool $enable
87 */
88 public function setEnablePersistentConnections($enable)
89 {
90 $this->enablePersistentConnections = $enable;
91 }
92 /**
93 * @return bool
94 */
95 public function getEnableHttp2()
96 {
97 return $this->enableHttp2;
98 }
99 /**
100 * @param bool $enable
101 */
102 public function setEnableHttp2($enable)
103 {
104 $this->enableHttp2 = $enable;
105 }
106 /**
107 * @return null|callable
108 */
109 public function getRequestStatusCallback()
110 {
111 return $this->requestStatusCallback;
112 }
113 /**
114 * Sets a callback that is called after each request. The callback will
115 * receive the following parameters:
116 * <ol>
117 * <li>string $rbody The response body</li>
118 * <li>integer $rcode The response status code</li>
119 * <li>\Stripe\Util\CaseInsensitiveArray $rheaders The response headers</li>
120 * <li>integer $errno The curl error number</li>
121 * <li>string|null $message The curl error message</li>
122 * <li>boolean $shouldRetry Whether the request will be retried</li>
123 * <li>integer $numRetries The number of the retry attempt</li>
124 * </ol>.
125 *
126 * @param null|callable $requestStatusCallback
127 */
128 public function setRequestStatusCallback($requestStatusCallback)
129 {
130 $this->requestStatusCallback = $requestStatusCallback;
131 }
132 // USER DEFINED TIMEOUTS
133 const DEFAULT_TIMEOUT = 80;
134 const DEFAULT_CONNECT_TIMEOUT = 30;
135 private $timeout = self::DEFAULT_TIMEOUT;
136 private $connectTimeout = self::DEFAULT_CONNECT_TIMEOUT;
137 public function setTimeout($seconds)
138 {
139 $this->timeout = (int) \max($seconds, 0);
140 return $this;
141 }
142 public function setConnectTimeout($seconds)
143 {
144 $this->connectTimeout = (int) \max($seconds, 0);
145 return $this;
146 }
147 public function getTimeout()
148 {
149 return $this->timeout;
150 }
151 public function getConnectTimeout()
152 {
153 return $this->connectTimeout;
154 }
155 // END OF USER DEFINED TIMEOUTS
156 /**
157 * @param 'delete'|'get'|'post' $method
158 * @param string $absUrl
159 * @param string $params
160 * @param bool $hasFile
161 * @param 'v1'|'v2' $apiMode
162 */
163 private function constructUrlAndBody($method, $absUrl, $params, $hasFile, $apiMode)
164 {
165 $params = Util\Util::objectsToIds($params);
166 if ('post' === $method) {
167 $absUrl = Util\Util::utf8($absUrl);
168 if ($hasFile) {
169 return [$absUrl, $params];
170 }
171 if ('v2' === $apiMode) {
172 if (\is_array($params) && 0 === \count($params)) {
173 // Send a request with empty body if we have no params set
174 // Setting the second parameter as null prevents the CURLOPT_POSTFIELDS
175 // from being set with the '[]', which is result of `json_encode([]).
176 return [$absUrl, null];
177 }
178 return [$absUrl, \json_encode($params)];
179 }
180 return [$absUrl, Util\Util::encodeParameters($params)];
181 }
182 if ($hasFile) {
183 throw new Exception\UnexpectedValueException("Unexpected. {$method} methods don't support file attachments");
184 }
185 if (0 === \count($params)) {
186 return [Util\Util::utf8($absUrl), null];
187 }
188 $encoded = Util\Util::encodeParameters($params, $apiMode);
189 $absUrl = "{$absUrl}?{$encoded}";
190 $absUrl = Util\Util::utf8($absUrl);
191 return [$absUrl, null];
192 }
193 private function calculateDefaultOptions($method, $absUrl, $headers, $params, $hasFile)
194 {
195 if (\is_callable($this->defaultOptions)) {
196 // call defaultOptions callback, set options to return value
197 $ret = \call_user_func_array($this->defaultOptions, [$method, $absUrl, $headers, $params, $hasFile]);
198 if (!\is_array($ret)) {
199 throw new Exception\UnexpectedValueException('Non-array value returned by defaultOptions CurlClient callback');
200 }
201 return $ret;
202 }
203 if (\is_array($this->defaultOptions)) {
204 // set default curlopts from array
205 return $this->defaultOptions;
206 }
207 return [];
208 }
209 private function constructCurlOptions($method, $absUrl, $headers, $body, $opts, $apiMode)
210 {
211 if ('get' === $method) {
212 $opts[\CURLOPT_HTTPGET] = 1;
213 } elseif ('post' === $method) {
214 $opts[\CURLOPT_POST] = 1;
215 } elseif ('delete' === $method) {
216 $opts[\CURLOPT_CUSTOMREQUEST] = 'DELETE';
217 } else {
218 throw new Exception\UnexpectedValueException("Unrecognized method {$method}");
219 }
220 if ($body) {
221 $opts[\CURLOPT_POSTFIELDS] = $body;
222 }
223 // this is a little verbose, but makes v1 vs v2 behavior really clear
224 if (!$this->hasHeader($headers, 'Idempotency-Key')) {
225 // all v2 requests should have an IK
226 if ('v2' === $apiMode) {
227 if ('post' === $method || 'delete' === $method) {
228 $headers[] = 'Idempotency-Key: ' . $this->randomGenerator->uuid();
229 }
230 } else if ('post' === $method && Stripe::$maxNetworkRetries > 0) {
231 $headers[] = 'Idempotency-Key: ' . $this->randomGenerator->uuid();
232 }
233 }
234 // By default for large request body sizes (> 1024 bytes), cURL will
235 // send a request without a body and with a `Expect: 100-continue`
236 // header, which gives the server a chance to respond with an error
237 // status code in cases where one can be determined right away (say
238 // on an authentication problem for example), and saves the "large"
239 // request body from being ever sent.
240 //
241 // Unfortunately, the bindings don't currently correctly handle the
242 // success case (in which the server sends back a 100 CONTINUE), so
243 // we'll error under that condition. To compensate for that problem
244 // for the time being, override cURL's behavior by simply always
245 // sending an empty `Expect:` header.
246 $headers[] = 'Expect: ';
247 $opts[\CURLOPT_URL] = $absUrl;
248 $opts[\CURLOPT_RETURNTRANSFER] = \true;
249 $opts[\CURLOPT_CONNECTTIMEOUT] = $this->connectTimeout;
250 $opts[\CURLOPT_TIMEOUT] = $this->timeout;
251 $opts[\CURLOPT_HTTPHEADER] = $headers;
252 $opts[\CURLOPT_CAINFO] = Stripe::getCABundlePath();
253 if (!Stripe::getVerifySslCerts()) {
254 $opts[\CURLOPT_SSL_VERIFYPEER] = \false;
255 }
256 if (!isset($opts[\CURLOPT_HTTP_VERSION]) && $this->getEnableHttp2()) {
257 // For HTTPS requests, enable HTTP/2, if supported
258 $opts[\CURLOPT_HTTP_VERSION] = \CURL_HTTP_VERSION_2TLS;
259 }
260 return $opts;
261 }
262 /**
263 * @param 'delete'|'get'|'post' $method
264 * @param string $absUrl
265 * @param array $headers
266 * @param array $params
267 * @param bool $hasFile
268 * @param 'v1'|'v2' $apiMode
269 */
270 private function constructRequest($method, $absUrl, $headers, $params, $hasFile, $apiMode)
271 {
272 $method = \strtolower($method);
273 $opts = $this->calculateDefaultOptions($method, $absUrl, $headers, $params, $hasFile);
274 list($absUrl, $body) = $this->constructUrlAndBody($method, $absUrl, $params, $hasFile, $apiMode);
275 $opts = $this->constructCurlOptions($method, $absUrl, $headers, $body, $opts, $apiMode);
276 return [$opts, $absUrl];
277 }
278 /**
279 * @param 'delete'|'get'|'post' $method
280 * @param string $absUrl
281 * @param array $headers
282 * @param array $params
283 * @param bool $hasFile
284 * @param 'v1'|'v2' $apiMode
285 */
286 public function request($method, $absUrl, $headers, $params, $hasFile, $apiMode = 'v1')
287 {
288 list($opts, $absUrl) = $this->constructRequest($method, $absUrl, $headers, $params, $hasFile, $apiMode);
289 list($rbody, $rcode, $rheaders) = $this->executeRequestWithRetries($opts, $absUrl);
290 return [$rbody, $rcode, $rheaders];
291 }
292 /**
293 * @param 'delete'|'get'|'post' $method
294 * @param string $absUrl
295 * @param array $headers
296 * @param array $params
297 * @param bool $hasFile
298 * @param callable $readBodyChunk
299 * @param 'v1'|'v2' $apiMode
300 */
301 public function requestStream($method, $absUrl, $headers, $params, $hasFile, $readBodyChunk, $apiMode = 'v1')
302 {
303 list($opts, $absUrl) = $this->constructRequest($method, $absUrl, $headers, $params, $hasFile, $apiMode);
304 $opts[\CURLOPT_RETURNTRANSFER] = \false;
305 list($rbody, $rcode, $rheaders) = $this->executeStreamingRequestWithRetries($opts, $absUrl, $readBodyChunk);
306 return [$rbody, $rcode, $rheaders];
307 }
308 /**
309 * Curl permits sending \CURLOPT_HEADERFUNCTION, which is called with lines
310 * from the header and \CURLOPT_WRITEFUNCTION, which is called with bytes
311 * from the body. You usually want to handle the body differently depending
312 * on what was in the header.
313 *
314 * This function makes it easier to specify different callbacks depending
315 * on the contents of the heeder. After the header has been completely read
316 * and the body begins to stream, it will call $determineWriteCallback with
317 * the array of headers. $determineWriteCallback should, based on the
318 * headers it receives, return a "writeCallback" that describes what to do
319 * with the incoming HTTP response body.
320 *
321 * @param array $opts
322 * @param callable $determineWriteCallback
323 *
324 * @return array
325 */
326 private function useHeadersToDetermineWriteCallback($opts, $determineWriteCallback)
327 {
328 $rheaders = new Util\CaseInsensitiveArray();
329 $headerCallback = function ($curl, $header_line) use (&$rheaders) {
330 return self::parseLineIntoHeaderArray($header_line, $rheaders);
331 };
332 $writeCallback = null;
333 $writeCallbackWrapper = function ($curl, $data) use (&$writeCallback, &$rheaders, &$determineWriteCallback) {
334 if (null === $writeCallback) {
335 $writeCallback = \call_user_func_array($determineWriteCallback, [$rheaders]);
336 }
337 return \call_user_func_array($writeCallback, [$curl, $data]);
338 };
339 return [$headerCallback, $writeCallbackWrapper];
340 }
341 private static function parseLineIntoHeaderArray($line, &$headers)
342 {
343 if (\false === \strpos($line, ':')) {
344 return \strlen($line);
345 }
346 list($key, $value) = \explode(':', \trim($line), 2);
347 $headers[\trim($key)] = \trim($value);
348 return \strlen($line);
349 }
350 /**
351 * Like `executeRequestWithRetries` except:
352 * 1. Does not buffer the body of a successful (status code < 300)
353 * response into memory -- instead, calls the caller-provided
354 * $readBodyChunk with each chunk of incoming data.
355 * 2. Does not retry if a network error occurs while streaming the
356 * body of a successful response.
357 *
358 * @param array $opts cURL options
359 * @param string $absUrl
360 * @param callable $readBodyChunk
361 *
362 * @return array
363 */
364 public function executeStreamingRequestWithRetries($opts, $absUrl, $readBodyChunk)
365 {
366 /** @var bool */
367 $shouldRetry = \false;
368 /** @var int */
369 $numRetries = 0;
370 // Will contain the bytes of the body of the last request
371 // if it was not successful and should not be retries
372 /** @var null|string */
373 $rbody = null;
374 // Status code of the last request
375 /** @var null|bool */
376 $rcode = null;
377 // Array of headers from the last request
378 /** @var null|array */
379 $lastRHeaders = null;
380 $errno = null;
381 $message = null;
382 $determineWriteCallback = function ($rheaders) use (&$readBodyChunk, &$shouldRetry, &$rbody, &$numRetries, &$rcode, &$lastRHeaders, &$errno) {
383 $lastRHeaders = $rheaders;
384 $errno = \curl_errno($this->curlHandle);
385 $rcode = \curl_getinfo($this->curlHandle, \CURLINFO_HTTP_CODE);
386 // Send the bytes from the body of a successful request to the caller-provided $readBodyChunk.
387 if ($rcode < 300) {
388 $rbody = null;
389 return function ($curl, $data) use (&$readBodyChunk) {
390 // Don't expose the $curl handle to the user, and don't require them to
391 // return the length of $data.
392 \call_user_func_array($readBodyChunk, [$data]);
393 return \strlen($data);
394 };
395 }
396 $shouldRetry = $this->shouldRetry($errno, $rcode, $rheaders, $numRetries);
397 // Discard the body from an unsuccessful request that should be retried.
398 if ($shouldRetry) {
399 return function ($curl, $data) {
400 return \strlen($data);
401 };
402 } else {
403 // Otherwise, buffer the body into $rbody. It will need to be parsed to determine
404 // which exception to throw to the user.
405 $rbody = '';
406 return function ($curl, $data) use (&$rbody) {
407 $rbody .= $data;
408 return \strlen($data);
409 };
410 }
411 };
412 while (\true) {
413 list($headerCallback, $writeCallback) = $this->useHeadersToDetermineWriteCallback($opts, $determineWriteCallback);
414 $opts[\CURLOPT_HEADERFUNCTION] = $headerCallback;
415 $opts[\CURLOPT_WRITEFUNCTION] = $writeCallback;
416 $shouldRetry = \false;
417 $rbody = null;
418 $this->resetCurlHandle();
419 \curl_setopt_array($this->curlHandle, $opts);
420 $result = \curl_exec($this->curlHandle);
421 $errno = \curl_errno($this->curlHandle);
422 if (0 !== $errno) {
423 $message = \curl_error($this->curlHandle);
424 }
425 if (!$this->getEnablePersistentConnections()) {
426 $this->closeCurlHandle();
427 }
428 if (\is_callable($this->getRequestStatusCallback())) {
429 \call_user_func_array($this->getRequestStatusCallback(), [$rbody, $rcode, $lastRHeaders, $errno, $message, $shouldRetry, $numRetries]);
430 }
431 if ($shouldRetry) {
432 ++$numRetries;
433 $sleepSeconds = $this->sleepTime($numRetries, $lastRHeaders);
434 \usleep((int) ($sleepSeconds * 1000000));
435 } else {
436 break;
437 }
438 }
439 if (0 !== $errno) {
440 $this->handleCurlError($absUrl, $errno, $message, $numRetries);
441 }
442 return [$rbody, $rcode, $lastRHeaders];
443 }
444 /**
445 * @param array $opts cURL options
446 * @param string $absUrl
447 */
448 public function executeRequestWithRetries($opts, $absUrl)
449 {
450 $numRetries = 0;
451 while (\true) {
452 $rcode = 0;
453 $errno = 0;
454 $message = null;
455 // Create a callback to capture HTTP headers for the response
456 $rheaders = new Util\CaseInsensitiveArray();
457 $headerCallback = function ($curl, $header_line) use (&$rheaders) {
458 return CurlClient::parseLineIntoHeaderArray($header_line, $rheaders);
459 };
460 $opts[\CURLOPT_HEADERFUNCTION] = $headerCallback;
461 $this->resetCurlHandle();
462 \curl_setopt_array($this->curlHandle, $opts);
463 $rbody = \curl_exec($this->curlHandle);
464 if (\false === $rbody) {
465 $errno = \curl_errno($this->curlHandle);
466 $message = \curl_error($this->curlHandle);
467 } else {
468 $rcode = \curl_getinfo($this->curlHandle, \CURLINFO_HTTP_CODE);
469 }
470 if (!$this->getEnablePersistentConnections()) {
471 $this->closeCurlHandle();
472 }
473 $shouldRetry = $this->shouldRetry($errno, $rcode, $rheaders, $numRetries);
474 if (\is_callable($this->getRequestStatusCallback())) {
475 \call_user_func_array($this->getRequestStatusCallback(), [$rbody, $rcode, $rheaders, $errno, $message, $shouldRetry, $numRetries]);
476 }
477 if ($shouldRetry) {
478 ++$numRetries;
479 $sleepSeconds = $this->sleepTime($numRetries, $rheaders);
480 \usleep((int) ($sleepSeconds * 1000000));
481 } else {
482 break;
483 }
484 }
485 if (\false === $rbody) {
486 $this->handleCurlError($absUrl, $errno, $message, $numRetries);
487 }
488 return [$rbody, $rcode, $rheaders];
489 }
490 /**
491 * @param string $url
492 * @param int $errno
493 * @param string $message
494 * @param int $numRetries
495 *
496 * @throws Exception\ApiConnectionException
497 */
498 private function handleCurlError($url, $errno, $message, $numRetries)
499 {
500 switch ($errno) {
501 case \CURLE_COULDNT_CONNECT:
502 case \CURLE_COULDNT_RESOLVE_HOST:
503 case \CURLE_OPERATION_TIMEOUTED:
504 $msg = "Could not connect to Stripe ({$url}). Please check your " . 'internet connection and try again. If this problem persists, ' . "you should check Stripe's service status at " . 'https://twitter.com/stripestatus, or';
505 break;
506 case \CURLE_SSL_CACERT:
507 case \CURLE_SSL_PEER_CERTIFICATE:
508 $msg = "Could not verify Stripe's SSL certificate. Please make sure " . 'that your network is not intercepting certificates. ' . "(Try going to {$url} in your browser.) " . 'If this problem persists,';
509 break;
510 default:
511 $msg = 'Unexpected error communicating with Stripe. ' . 'If this problem persists,';
512 }
513 $msg .= ' let us know at support@stripe.com.';
514 $msg .= "\n\n(Network error [errno {$errno}]: {$message})";
515 if ($numRetries > 0) {
516 $msg .= "\n\nRequest was retried {$numRetries} times.";
517 }
518 throw new Exception\ApiConnectionException($msg);
519 }
520 /**
521 * Checks if an error is a problem that we should retry on. This includes both
522 * socket errors that may represent an intermittent problem and some special
523 * HTTP statuses.
524 *
525 * @param int $errno
526 * @param int $rcode
527 * @param array|\Stripe\Util\CaseInsensitiveArray $rheaders
528 * @param int $numRetries
529 *
530 * @return bool
531 */
532 private function shouldRetry($errno, $rcode, $rheaders, $numRetries)
533 {
534 if ($numRetries >= Stripe::getMaxNetworkRetries()) {
535 return \false;
536 }
537 // Retry on timeout-related problems (either on open or read).
538 if (\CURLE_OPERATION_TIMEOUTED === $errno) {
539 return \true;
540 }
541 // Destination refused the connection, the connection was reset, or a
542 // variety of other connection failures. This could occur from a single
543 // saturated server, so retry in case it's intermittent.
544 if (\CURLE_COULDNT_CONNECT === $errno) {
545 return \true;
546 }
547 // The API may ask us not to retry (eg; if doing so would be a no-op)
548 // or advise us to retry (eg; in cases of lock timeouts); we defer to that.
549 if (isset($rheaders['stripe-should-retry'])) {
550 if ('false' === $rheaders['stripe-should-retry']) {
551 return \false;
552 }
553 if ('true' === $rheaders['stripe-should-retry']) {
554 return \true;
555 }
556 }
557 // 409 Conflict
558 if (409 === $rcode) {
559 return \true;
560 }
561 // Retry on 500, 503, and other internal errors.
562 //
563 // Note that we expect the stripe-should-retry header to be false
564 // in most cases when a 500 is returned, since our idempotency framework
565 // would typically replay it anyway.
566 if ($rcode >= 500) {
567 return \true;
568 }
569 return \false;
570 }
571 /**
572 * Provides the number of seconds to wait before retrying a request.
573 *
574 * @param int $numRetries
575 * @param array|\Stripe\Util\CaseInsensitiveArray $rheaders
576 *
577 * @return int
578 */
579 private function sleepTime($numRetries, $rheaders)
580 {
581 // Apply exponential backoff with $initialNetworkRetryDelay on the
582 // number of $numRetries so far as inputs. Do not allow the number to exceed
583 // $maxNetworkRetryDelay.
584 $sleepSeconds = \min(Stripe::getInitialNetworkRetryDelay() * 1.0 * 2 ** ($numRetries - 1), Stripe::getMaxNetworkRetryDelay());
585 // Apply some jitter by randomizing the value in the range of
586 // ($sleepSeconds / 2) to ($sleepSeconds).
587 $sleepSeconds *= 0.5 * (1 + $this->randomGenerator->randFloat());
588 // But never sleep less than the base sleep seconds.
589 $sleepSeconds = \max(Stripe::getInitialNetworkRetryDelay(), $sleepSeconds);
590 // And never sleep less than the time the API asks us to wait, assuming it's a reasonable ask.
591 $retryAfter = isset($rheaders['retry-after']) ? (float) $rheaders['retry-after'] : 0.0;
592 if (\floor($retryAfter) === $retryAfter && $retryAfter <= Stripe::getMaxRetryAfter()) {
593 $sleepSeconds = \max($sleepSeconds, $retryAfter);
594 }
595 return $sleepSeconds;
596 }
597 /**
598 * Initializes the curl handle. If already initialized, the handle is closed first.
599 */
600 private function initCurlHandle()
601 {
602 $this->closeCurlHandle();
603 $this->curlHandle = \curl_init();
604 }
605 /**
606 * Closes the curl handle if initialized. Do nothing if already closed.
607 */
608 private function closeCurlHandle()
609 {
610 if (null !== $this->curlHandle) {
611 \curl_close($this->curlHandle);
612 $this->curlHandle = null;
613 }
614 }
615 /**
616 * Resets the curl handle. If the handle is not already initialized, or if persistent
617 * connections are disabled, the handle is reinitialized instead.
618 */
619 private function resetCurlHandle()
620 {
621 if (null !== $this->curlHandle && $this->getEnablePersistentConnections()) {
622 \curl_reset($this->curlHandle);
623 } else {
624 $this->initCurlHandle();
625 }
626 }
627 /**
628 * Indicates whether it is safe to use HTTP/2 or not.
629 *
630 * @return bool
631 */
632 private function canSafelyUseHttp2()
633 {
634 // Versions of curl older than 7.60.0 don't respect GOAWAY frames
635 // (cf. https://github.com/curl/curl/issues/2416), which Stripe use.
636 $curlVersion = \curl_version()['version'];
637 return \version_compare($curlVersion, '7.60.0') >= 0;
638 }
639 /**
640 * Checks if a list of headers contains a specific header name.
641 *
642 * @param string[] $headers
643 * @param string $name
644 *
645 * @return bool
646 */
647 private function hasHeader($headers, $name)
648 {
649 foreach ($headers as $header) {
650 if (0 === \strncasecmp($header, "{$name}: ", \strlen($name) + 2)) {
651 return \true;
652 }
653 }
654 return \false;
655 }
656 }
657