2
0
forked from Wavyzz/dolibarr

update stripe-php 7.67.0

This commit is contained in:
ptibogxiv
2021-01-03 23:37:44 +01:00
parent b0738643fb
commit 984c355f39
226 changed files with 14453 additions and 4860 deletions

View File

@@ -2,28 +2,26 @@
namespace Stripe\HttpClient;
use Stripe\Exception;
use Stripe\Stripe;
use Stripe\Error;
use Stripe\Util;
// cURL constants are not defined in PHP < 5.5
// @codingStandardsIgnoreStart
// PSR2 requires all constants be upper case. Sadly, the CURL_SSLVERSION
// constants do not abide by those rules.
// Note the values 1 and 6 come from their position in the enum that
// Note the values come from their position in the enums that
// defines them in cURL's source code.
if (!defined('CURL_SSLVERSION_TLSv1')) {
define('CURL_SSLVERSION_TLSv1', 1);
}
if (!defined('CURL_SSLVERSION_TLSv1_2')) {
define('CURL_SSLVERSION_TLSv1_2', 6);
// Available since PHP 5.5.19 and 5.6.3
if (!\defined('CURL_SSLVERSION_TLSv1_2')) {
\define('CURL_SSLVERSION_TLSv1_2', 6);
}
// @codingStandardsIgnoreEnd
if (!defined('CURL_HTTP_VERSION_2TLS')) {
define('CURL_HTTP_VERSION_2TLS', 4);
// Available since PHP 7.0.7 and cURL 7.47.0
if (!\defined('CURL_HTTP_VERSION_2TLS')) {
\define('CURL_HTTP_VERSION_2TLS', 4);
}
class CurlClient implements ClientInterface
@@ -35,18 +33,24 @@ class CurlClient implements ClientInterface
if (!self::$instance) {
self::$instance = new self();
}
return self::$instance;
}
protected $defaultOptions;
/** @var \Stripe\Util\RandomGenerator */
protected $randomGenerator;
protected $userAgentInfo;
protected $enablePersistentConnections = null;
protected $enablePersistentConnections = true;
protected $enableHttp2 = null;
protected $enableHttp2;
protected $curlHandle = null;
protected $curlHandle;
protected $requestStatusCallback;
/**
* CurlClient constructor.
@@ -59,7 +63,8 @@ class CurlClient implements ClientInterface
* Note that request() will silently ignore a non-callable, non-array $defaultOptions, and will
* throw an exception if $defaultOptions returns a non-array value.
*
* @param array|callable|null $defaultOptions
* @param null|array|callable $defaultOptions
* @param null|\Stripe\Util\RandomGenerator $randomGenerator
*/
public function __construct($defaultOptions = null, $randomGenerator = null)
{
@@ -67,10 +72,6 @@ class CurlClient implements ClientInterface
$this->randomGenerator = $randomGenerator ?: new Util\RandomGenerator();
$this->initUserAgentInfo();
// TODO: curl_reset requires PHP >= 5.5.0. Once we drop support for PHP 5.4, we can simply
// initialize this to true.
$this->enablePersistentConnections = function_exists('curl_reset');
$this->enableHttp2 = $this->canSafelyUseHttp2();
}
@@ -81,9 +82,9 @@ class CurlClient implements ClientInterface
public function initUserAgentInfo()
{
$curlVersion = curl_version();
$curlVersion = \curl_version();
$this->userAgentInfo = [
'httplib' => 'curl ' . $curlVersion['version'],
'httplib' => 'curl ' . $curlVersion['version'],
'ssllib' => $curlVersion['ssl_version'],
];
}
@@ -99,7 +100,7 @@ class CurlClient implements ClientInterface
}
/**
* @return boolean
* @return bool
*/
public function getEnablePersistentConnections()
{
@@ -107,7 +108,7 @@ class CurlClient implements ClientInterface
}
/**
* @param boolean $enable
* @param bool $enable
*/
public function setEnablePersistentConnections($enable)
{
@@ -115,7 +116,7 @@ class CurlClient implements ClientInterface
}
/**
* @return boolean
* @return bool
*/
public function getEnableHttp2()
{
@@ -123,13 +124,41 @@ class CurlClient implements ClientInterface
}
/**
* @param boolean $enable
* @param bool $enable
*/
public function setEnableHttp2($enable)
{
$this->enableHttp2 = $enable;
}
/**
* @return null|callable
*/
public function getRequestStatusCallback()
{
return $this->requestStatusCallback;
}
/**
* Sets a callback that is called after each request. The callback will
* receive the following parameters:
* <ol>
* <li>string $rbody The response body</li>
* <li>integer $rcode The response status code</li>
* <li>\Stripe\Util\CaseInsensitiveArray $rheaders The response headers</li>
* <li>integer $errno The curl error number</li>
* <li>string|null $message The curl error message</li>
* <li>boolean $shouldRetry Whether the request will be retried</li>
* <li>integer $numRetries The number of the retry attempt</li>
* </ol>.
*
* @param null|callable $requestStatusCallback
*/
public function setRequestStatusCallback($requestStatusCallback)
{
$this->requestStatusCallback = $requestStatusCallback;
}
// USER DEFINED TIMEOUTS
const DEFAULT_TIMEOUT = 80;
@@ -140,13 +169,15 @@ class CurlClient implements ClientInterface
public function setTimeout($seconds)
{
$this->timeout = (int) max($seconds, 0);
$this->timeout = (int) \max($seconds, 0);
return $this;
}
public function setConnectTimeout($seconds)
{
$this->connectTimeout = (int) max($seconds, 0);
$this->connectTimeout = (int) \max($seconds, 0);
return $this;
}
@@ -164,64 +195,52 @@ class CurlClient implements ClientInterface
public function request($method, $absUrl, $headers, $params, $hasFile)
{
$method = strtolower($method);
$method = \strtolower($method);
$opts = [];
if (is_callable($this->defaultOptions)) { // call defaultOptions callback, set options to return value
$opts = call_user_func_array($this->defaultOptions, func_get_args());
if (!is_array($opts)) {
throw new Error\Api("Non-array value returned by defaultOptions CurlClient callback");
if (\is_callable($this->defaultOptions)) { // call defaultOptions callback, set options to return value
$opts = \call_user_func_array($this->defaultOptions, \func_get_args());
if (!\is_array($opts)) {
throw new Exception\UnexpectedValueException('Non-array value returned by defaultOptions CurlClient callback');
}
} elseif (is_array($this->defaultOptions)) { // set default curlopts from array
} elseif (\is_array($this->defaultOptions)) { // set default curlopts from array
$opts = $this->defaultOptions;
}
$params = Util\Util::objectsToIds($params);
if ($method == 'get') {
if ('get' === $method) {
if ($hasFile) {
throw new Error\Api(
"Issuing a GET request with a file parameter"
throw new Exception\UnexpectedValueException(
'Issuing a GET request with a file parameter'
);
}
$opts[CURLOPT_HTTPGET] = 1;
if (count($params) > 0) {
$opts[\CURLOPT_HTTPGET] = 1;
if (\count($params) > 0) {
$encoded = Util\Util::encodeParameters($params);
$absUrl = "$absUrl?$encoded";
$absUrl = "{$absUrl}?{$encoded}";
}
} elseif ($method == 'post') {
$opts[CURLOPT_POST] = 1;
$opts[CURLOPT_POSTFIELDS] = $hasFile ? $params : Util\Util::encodeParameters($params);
} elseif ($method == 'delete') {
$opts[CURLOPT_CUSTOMREQUEST] = 'DELETE';
if (count($params) > 0) {
} elseif ('post' === $method) {
$opts[\CURLOPT_POST] = 1;
$opts[\CURLOPT_POSTFIELDS] = $hasFile ? $params : Util\Util::encodeParameters($params);
} elseif ('delete' === $method) {
$opts[\CURLOPT_CUSTOMREQUEST] = 'DELETE';
if (\count($params) > 0) {
$encoded = Util\Util::encodeParameters($params);
$absUrl = "$absUrl?$encoded";
$absUrl = "{$absUrl}?{$encoded}";
}
} else {
throw new Error\Api("Unrecognized method $method");
throw new Exception\UnexpectedValueException("Unrecognized method {$method}");
}
// It is only safe to retry network failures on POST requests if we
// add an Idempotency-Key header
if (($method == 'post') && (Stripe::$maxNetworkRetries > 0)) {
if (!$this->hasHeader($headers, "Idempotency-Key")) {
array_push($headers, 'Idempotency-Key: ' . $this->randomGenerator->uuid());
if (('post' === $method) && (Stripe::$maxNetworkRetries > 0)) {
if (!$this->hasHeader($headers, 'Idempotency-Key')) {
$headers[] = 'Idempotency-Key: ' . $this->randomGenerator->uuid();
}
}
// Create a callback to capture HTTP headers for the response
$rheaders = new Util\CaseInsensitiveArray();
$headerCallback = function ($curl, $header_line) use (&$rheaders) {
// Ignore the HTTP request line (HTTP/1.1 200 OK)
if (strpos($header_line, ":") === false) {
return strlen($header_line);
}
list($key, $value) = explode(":", trim($header_line), 2);
$rheaders[trim($key)] = trim($value);
return strlen($header_line);
};
// By default for large request body sizes (> 1024 bytes), cURL will
// send a request without a body and with a `Expect: 100-continue`
// header, which gives the server a chance to respond with an error
@@ -234,32 +253,36 @@ class CurlClient implements ClientInterface
// we'll error under that condition. To compensate for that problem
// for the time being, override cURL's behavior by simply always
// sending an empty `Expect:` header.
array_push($headers, 'Expect: ');
$headers[] = 'Expect: ';
$absUrl = Util\Util::utf8($absUrl);
$opts[CURLOPT_URL] = $absUrl;
$opts[CURLOPT_RETURNTRANSFER] = true;
$opts[CURLOPT_CONNECTTIMEOUT] = $this->connectTimeout;
$opts[CURLOPT_TIMEOUT] = $this->timeout;
$opts[CURLOPT_HEADERFUNCTION] = $headerCallback;
$opts[CURLOPT_HTTPHEADER] = $headers;
$opts[CURLOPT_CAINFO] = Stripe::getCABundlePath();
$opts[\CURLOPT_URL] = $absUrl;
$opts[\CURLOPT_RETURNTRANSFER] = true;
$opts[\CURLOPT_CONNECTTIMEOUT] = $this->connectTimeout;
$opts[\CURLOPT_TIMEOUT] = $this->timeout;
$opts[\CURLOPT_HTTPHEADER] = $headers;
$opts[\CURLOPT_CAINFO] = Stripe::getCABundlePath();
if (!Stripe::getVerifySslCerts()) {
$opts[CURLOPT_SSL_VERIFYPEER] = false;
$opts[\CURLOPT_SSL_VERIFYPEER] = false;
}
if (!isset($opts[CURLOPT_HTTP_VERSION]) && $this->getEnableHttp2()) {
if (!isset($opts[\CURLOPT_HTTP_VERSION]) && $this->getEnableHttp2()) {
// For HTTPS requests, enable HTTP/2, if supported
$opts[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_2TLS;
$opts[\CURLOPT_HTTP_VERSION] = \CURL_HTTP_VERSION_2TLS;
}
list($rbody, $rcode) = $this->executeRequestWithRetries($opts, $absUrl);
// Stripe's API servers are only accessible over IPv4. Force IPv4 resolving to avoid
// potential issues (cf. https://github.com/stripe/stripe-php/issues/1045).
$opts[\CURLOPT_IPRESOLVE] = \CURL_IPRESOLVE_V4;
list($rbody, $rcode, $rheaders) = $this->executeRequestWithRetries($opts, $absUrl);
return [$rbody, $rcode, $rheaders];
}
/**
* @param array $opts cURL options
* @param string $absUrl
*/
private function executeRequestWithRetries($opts, $absUrl)
{
@@ -268,35 +291,59 @@ class CurlClient implements ClientInterface
while (true) {
$rcode = 0;
$errno = 0;
$message = null;
// Create a callback to capture HTTP headers for the response
$rheaders = new Util\CaseInsensitiveArray();
$headerCallback = function ($curl, $header_line) use (&$rheaders) {
// Ignore the HTTP request line (HTTP/1.1 200 OK)
if (false === \strpos($header_line, ':')) {
return \strlen($header_line);
}
list($key, $value) = \explode(':', \trim($header_line), 2);
$rheaders[\trim($key)] = \trim($value);
return \strlen($header_line);
};
$opts[\CURLOPT_HEADERFUNCTION] = $headerCallback;
$this->resetCurlHandle();
curl_setopt_array($this->curlHandle, $opts);
$rbody = curl_exec($this->curlHandle);
\curl_setopt_array($this->curlHandle, $opts);
$rbody = \curl_exec($this->curlHandle);
if ($rbody === false) {
$errno = curl_errno($this->curlHandle);
$message = curl_error($this->curlHandle);
if (false === $rbody) {
$errno = \curl_errno($this->curlHandle);
$message = \curl_error($this->curlHandle);
} else {
$rcode = curl_getinfo($this->curlHandle, CURLINFO_HTTP_CODE);
$rcode = \curl_getinfo($this->curlHandle, \CURLINFO_HTTP_CODE);
}
if (!$this->getEnablePersistentConnections()) {
$this->closeCurlHandle();
}
if ($this->shouldRetry($errno, $rcode, $numRetries)) {
$numRetries += 1;
$sleepSeconds = $this->sleepTime($numRetries);
usleep(intval($sleepSeconds * 1000000));
$shouldRetry = $this->shouldRetry($errno, $rcode, $rheaders, $numRetries);
if (\is_callable($this->getRequestStatusCallback())) {
\call_user_func_array(
$this->getRequestStatusCallback(),
[$rbody, $rcode, $rheaders, $errno, $message, $shouldRetry, $numRetries]
);
}
if ($shouldRetry) {
++$numRetries;
$sleepSeconds = $this->sleepTime($numRetries, $rheaders);
\usleep((int) ($sleepSeconds * 1000000));
} else {
break;
}
}
if ($rbody === false) {
if (false === $rbody) {
$this->handleCurlError($absUrl, $errno, $message, $numRetries);
}
return [$rbody, $rcode];
return [$rbody, $rcode, $rheaders];
}
/**
@@ -304,83 +351,119 @@ class CurlClient implements ClientInterface
* @param int $errno
* @param string $message
* @param int $numRetries
* @throws Error\ApiConnection
*
* @throws Exception\ApiConnectionException
*/
private function handleCurlError($url, $errno, $message, $numRetries)
{
switch ($errno) {
case CURLE_COULDNT_CONNECT:
case CURLE_COULDNT_RESOLVE_HOST:
case CURLE_OPERATION_TIMEOUTED:
$msg = "Could not connect to Stripe ($url). Please check your "
. "internet connection and try again. If this problem persists, "
case \CURLE_COULDNT_CONNECT:
case \CURLE_COULDNT_RESOLVE_HOST:
case \CURLE_OPERATION_TIMEOUTED:
$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";
break;
case CURLE_SSL_CACERT:
case CURLE_SSL_PEER_CERTIFICATE:
$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,";
break;
default:
$msg = "Unexpected error communicating with Stripe. "
. "If this problem persists,";
}
$msg .= " let us know at support@stripe.com.";
. 'https://twitter.com/stripestatus, or';
$msg .= "\n\n(Network error [errno $errno]: $message)";
break;
case \CURLE_SSL_CACERT:
case \CURLE_SSL_PEER_CERTIFICATE:
$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,';
break;
default:
$msg = 'Unexpected error communicating with Stripe. '
. 'If this problem persists,';
}
$msg .= ' let us know at support@stripe.com.';
$msg .= "\n\n(Network error [errno {$errno}]: {$message})";
if ($numRetries > 0) {
$msg .= "\n\nRequest was retried $numRetries times.";
$msg .= "\n\nRequest was retried {$numRetries} times.";
}
throw new Error\ApiConnection($msg);
throw new Exception\ApiConnectionException($msg);
}
/**
* Checks if an error is a problem that we should retry on. This includes both
* socket errors that may represent an intermittent problem and some special
* HTTP statuses.
*
* @param int $errno
* @param int $rcode
* @param array|\Stripe\Util\CaseInsensitiveArray $rheaders
* @param int $numRetries
*
* @return bool
*/
private function shouldRetry($errno, $rcode, $numRetries)
private function shouldRetry($errno, $rcode, $rheaders, $numRetries)
{
if ($numRetries >= Stripe::getMaxNetworkRetries()) {
return false;
}
// Retry on timeout-related problems (either on open or read).
if ($errno === CURLE_OPERATION_TIMEOUTED) {
if (\CURLE_OPERATION_TIMEOUTED === $errno) {
return true;
}
// Destination refused the connection, the connection was reset, or a
// variety of other connection failures. This could occur from a single
// saturated server, so retry in case it's intermittent.
if ($errno === CURLE_COULDNT_CONNECT) {
if (\CURLE_COULDNT_CONNECT === $errno) {
return true;
}
// 409 conflict
if ($rcode === 409) {
// The API may ask us not to retry (eg; if doing so would be a no-op)
// or advise us to retry (eg; in cases of lock timeouts); we defer to that.
if (isset($rheaders['stripe-should-retry'])) {
if ('false' === $rheaders['stripe-should-retry']) {
return false;
}
if ('true' === $rheaders['stripe-should-retry']) {
return true;
}
}
// 409 Conflict
if (409 === $rcode) {
return true;
}
// Retry on 500, 503, and other internal errors.
//
// Note that we expect the stripe-should-retry header to be false
// in most cases when a 500 is returned, since our idempotency framework
// would typically replay it anyway.
if ($rcode >= 500) {
return true;
}
return false;
}
private function sleepTime($numRetries)
/**
* Provides the number of seconds to wait before retrying a request.
*
* @param int $numRetries
* @param array|\Stripe\Util\CaseInsensitiveArray $rheaders
*
* @return int
*/
private function sleepTime($numRetries, $rheaders)
{
// Apply exponential backoff with $initialNetworkRetryDelay on the
// number of $numRetries so far as inputs. Do not allow the number to exceed
// $maxNetworkRetryDelay.
$sleepSeconds = min(
Stripe::getInitialNetworkRetryDelay() * 1.0 * pow(2, $numRetries - 1),
$sleepSeconds = \min(
Stripe::getInitialNetworkRetryDelay() * 1.0 * 2 ** ($numRetries - 1),
Stripe::getMaxNetworkRetryDelay()
);
@@ -389,7 +472,13 @@ class CurlClient implements ClientInterface
$sleepSeconds *= 0.5 * (1 + $this->randomGenerator->randFloat());
// But never sleep less than the base sleep seconds.
$sleepSeconds = max(Stripe::getInitialNetworkRetryDelay(), $sleepSeconds);
$sleepSeconds = \max(Stripe::getInitialNetworkRetryDelay(), $sleepSeconds);
// And never sleep less than the time the API asks us to wait, assuming it's a reasonable ask.
$retryAfter = isset($rheaders['retry-after']) ? (float) ($rheaders['retry-after']) : 0.0;
if (\floor($retryAfter) === $retryAfter && $retryAfter <= Stripe::getMaxRetryAfter()) {
$sleepSeconds = \max($sleepSeconds, $retryAfter);
}
return $sleepSeconds;
}
@@ -400,7 +489,7 @@ class CurlClient implements ClientInterface
private function initCurlHandle()
{
$this->closeCurlHandle();
$this->curlHandle = curl_init();
$this->curlHandle = \curl_init();
}
/**
@@ -408,8 +497,8 @@ class CurlClient implements ClientInterface
*/
private function closeCurlHandle()
{
if (!is_null($this->curlHandle)) {
curl_close($this->curlHandle);
if (null !== $this->curlHandle) {
\curl_close($this->curlHandle);
$this->curlHandle = null;
}
}
@@ -420,8 +509,8 @@ class CurlClient implements ClientInterface
*/
private function resetCurlHandle()
{
if (!is_null($this->curlHandle) && $this->getEnablePersistentConnections()) {
curl_reset($this->curlHandle);
if (null !== $this->curlHandle && $this->getEnablePersistentConnections()) {
\curl_reset($this->curlHandle);
} else {
$this->initCurlHandle();
}
@@ -430,14 +519,15 @@ class CurlClient implements ClientInterface
/**
* Indicates whether it is safe to use HTTP/2 or not.
*
* @return boolean
* @return bool
*/
private function canSafelyUseHttp2()
{
// Versions of curl older than 7.60.0 don't respect GOAWAY frames
// (cf. https://github.com/curl/curl/issues/2416), which Stripe use.
$curlVersion = curl_version()['version'];
return (version_compare($curlVersion, '7.60.0') >= 0);
$curlVersion = \curl_version()['version'];
return \version_compare($curlVersion, '7.60.0') >= 0;
}
/**
@@ -445,12 +535,13 @@ class CurlClient implements ClientInterface
*
* @param string[] $headers
* @param string $name
* @return boolean
*
* @return bool
*/
private function hasHeader($headers, $name)
{
foreach ($headers as $header) {
if (strncasecmp($header, "{$name}: ", strlen($name) + 2) === 0) {
if (0 === \strncasecmp($header, "{$name}: ", \strlen($name) + 2)) {
return true;
}
}