mirror of
https://gitlab.com/openlp/website.git
synced 2024-12-22 13:02:50 +00:00
300 lines
9.6 KiB
PHP
300 lines
9.6 KiB
PHP
|
<?php
|
||
|
|
||
|
namespace Stripe\HttpClient;
|
||
|
|
||
|
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
|
||
|
// 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);
|
||
|
}
|
||
|
// @codingStandardsIgnoreEnd
|
||
|
|
||
|
class CurlClient implements ClientInterface
|
||
|
{
|
||
|
private static $instance;
|
||
|
|
||
|
public static function instance()
|
||
|
{
|
||
|
if (!self::$instance) {
|
||
|
self::$instance = new self();
|
||
|
}
|
||
|
return self::$instance;
|
||
|
}
|
||
|
|
||
|
protected $defaultOptions;
|
||
|
|
||
|
protected $userAgentInfo;
|
||
|
|
||
|
/**
|
||
|
* CurlClient constructor.
|
||
|
*
|
||
|
* Pass in a callable to $defaultOptions that returns an array of CURLOPT_* values to start
|
||
|
* off a request with, or an flat array with the same format used by curl_setopt_array() to
|
||
|
* provide a static set of options. Note that many options are overridden later in the request
|
||
|
* call, including timeouts, which can be set via setTimeout() and setConnectTimeout().
|
||
|
*
|
||
|
* 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
|
||
|
*/
|
||
|
public function __construct($defaultOptions = null)
|
||
|
{
|
||
|
$this->defaultOptions = $defaultOptions;
|
||
|
$this->initUserAgentInfo();
|
||
|
}
|
||
|
|
||
|
public function initUserAgentInfo()
|
||
|
{
|
||
|
$curlVersion = curl_version();
|
||
|
$this->userAgentInfo = array(
|
||
|
'httplib' => 'curl ' . $curlVersion['version'],
|
||
|
'ssllib' => $curlVersion['ssl_version'],
|
||
|
);
|
||
|
}
|
||
|
|
||
|
public function getDefaultOptions()
|
||
|
{
|
||
|
return $this->defaultOptions;
|
||
|
}
|
||
|
|
||
|
public function getUserAgentInfo()
|
||
|
{
|
||
|
return $this->userAgentInfo;
|
||
|
}
|
||
|
|
||
|
// USER DEFINED TIMEOUTS
|
||
|
|
||
|
const DEFAULT_TIMEOUT = 80;
|
||
|
const DEFAULT_CONNECT_TIMEOUT = 30;
|
||
|
|
||
|
private $timeout = self::DEFAULT_TIMEOUT;
|
||
|
private $connectTimeout = self::DEFAULT_CONNECT_TIMEOUT;
|
||
|
|
||
|
public function setTimeout($seconds)
|
||
|
{
|
||
|
$this->timeout = (int) max($seconds, 0);
|
||
|
return $this;
|
||
|
}
|
||
|
|
||
|
public function setConnectTimeout($seconds)
|
||
|
{
|
||
|
$this->connectTimeout = (int) max($seconds, 0);
|
||
|
return $this;
|
||
|
}
|
||
|
|
||
|
public function getTimeout()
|
||
|
{
|
||
|
return $this->timeout;
|
||
|
}
|
||
|
|
||
|
public function getConnectTimeout()
|
||
|
{
|
||
|
return $this->connectTimeout;
|
||
|
}
|
||
|
|
||
|
// END OF USER DEFINED TIMEOUTS
|
||
|
|
||
|
public function request($method, $absUrl, $headers, $params, $hasFile)
|
||
|
{
|
||
|
$curl = curl_init();
|
||
|
$method = strtolower($method);
|
||
|
|
||
|
$opts = array();
|
||
|
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");
|
||
|
}
|
||
|
} elseif (is_array($this->defaultOptions)) { // set default curlopts from array
|
||
|
$opts = $this->defaultOptions;
|
||
|
}
|
||
|
|
||
|
if ($method == 'get') {
|
||
|
if ($hasFile) {
|
||
|
throw new Error\Api(
|
||
|
"Issuing a GET request with a file parameter"
|
||
|
);
|
||
|
}
|
||
|
$opts[CURLOPT_HTTPGET] = 1;
|
||
|
if (count($params) > 0) {
|
||
|
$encoded = self::encode($params);
|
||
|
$absUrl = "$absUrl?$encoded";
|
||
|
}
|
||
|
} elseif ($method == 'post') {
|
||
|
$opts[CURLOPT_POST] = 1;
|
||
|
$opts[CURLOPT_POSTFIELDS] = $hasFile ? $params : self::encode($params);
|
||
|
} elseif ($method == 'delete') {
|
||
|
$opts[CURLOPT_CUSTOMREQUEST] = 'DELETE';
|
||
|
if (count($params) > 0) {
|
||
|
$encoded = self::encode($params);
|
||
|
$absUrl = "$absUrl?$encoded";
|
||
|
}
|
||
|
} else {
|
||
|
throw new Error\Api("Unrecognized method $method");
|
||
|
}
|
||
|
|
||
|
// Create a callback to capture HTTP headers for the response
|
||
|
$rheaders = array();
|
||
|
$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
|
||
|
// status code in cases where one can be determined right away (say
|
||
|
// on an authentication problem for example), and saves the "large"
|
||
|
// request body from being ever sent.
|
||
|
//
|
||
|
// Unfortunately, the bindings don't currently correctly handle the
|
||
|
// success case (in which the server sends back a 100 CONTINUE), so
|
||
|
// 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: ');
|
||
|
|
||
|
$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;
|
||
|
if (!Stripe::$verifySslCerts) {
|
||
|
$opts[CURLOPT_SSL_VERIFYPEER] = false;
|
||
|
}
|
||
|
|
||
|
curl_setopt_array($curl, $opts);
|
||
|
$rbody = curl_exec($curl);
|
||
|
|
||
|
if (!defined('CURLE_SSL_CACERT_BADFILE')) {
|
||
|
define('CURLE_SSL_CACERT_BADFILE', 77); // constant not defined in PHP
|
||
|
}
|
||
|
|
||
|
$errno = curl_errno($curl);
|
||
|
if ($errno == CURLE_SSL_CACERT ||
|
||
|
$errno == CURLE_SSL_PEER_CERTIFICATE ||
|
||
|
$errno == CURLE_SSL_CACERT_BADFILE
|
||
|
) {
|
||
|
array_push(
|
||
|
$headers,
|
||
|
'X-Stripe-Client-Info: {"ca":"using Stripe-supplied CA bundle"}'
|
||
|
);
|
||
|
$cert = self::caBundle();
|
||
|
curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
|
||
|
curl_setopt($curl, CURLOPT_CAINFO, $cert);
|
||
|
$rbody = curl_exec($curl);
|
||
|
}
|
||
|
|
||
|
if ($rbody === false) {
|
||
|
$errno = curl_errno($curl);
|
||
|
$message = curl_error($curl);
|
||
|
curl_close($curl);
|
||
|
$this->handleCurlError($absUrl, $errno, $message);
|
||
|
}
|
||
|
|
||
|
$rcode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
|
||
|
curl_close($curl);
|
||
|
return array($rbody, $rcode, $rheaders);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @param number $errno
|
||
|
* @param string $message
|
||
|
* @throws Error\ApiConnection
|
||
|
*/
|
||
|
private function handleCurlError($url, $errno, $message)
|
||
|
{
|
||
|
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, "
|
||
|
. "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.";
|
||
|
|
||
|
$msg .= "\n\n(Network error [errno $errno]: $message)";
|
||
|
throw new Error\ApiConnection($msg);
|
||
|
}
|
||
|
|
||
|
private static function caBundle()
|
||
|
{
|
||
|
return dirname(__FILE__) . '/../../data/ca-certificates.crt';
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @param array $arr An map of param keys to values.
|
||
|
* @param string|null $prefix
|
||
|
*
|
||
|
* Only public for testability, should not be called outside of CurlClient
|
||
|
*
|
||
|
* @return string A querystring, essentially.
|
||
|
*/
|
||
|
public static function encode($arr, $prefix = null)
|
||
|
{
|
||
|
if (!is_array($arr)) {
|
||
|
return $arr;
|
||
|
}
|
||
|
|
||
|
$r = array();
|
||
|
foreach ($arr as $k => $v) {
|
||
|
if (is_null($v)) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if ($prefix) {
|
||
|
if ($k !== null && (!is_int($k) || is_array($v))) {
|
||
|
$k = $prefix."[".$k."]";
|
||
|
} else {
|
||
|
$k = $prefix."[]";
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (is_array($v)) {
|
||
|
$enc = self::encode($v, $k);
|
||
|
if ($enc) {
|
||
|
$r[] = $enc;
|
||
|
}
|
||
|
} else {
|
||
|
$r[] = urlencode($k)."=".urlencode($v);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return implode("&", $r);
|
||
|
}
|
||
|
}
|