| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282 |
- <?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;
- /**
- * 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;
- }
- public function getDefaultOptions()
- {
- return $this->defaultOptions;
- }
- // 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);
- }
- }
|