CurlClient.php 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298
  1. <?php
  2. namespace Stripe\HttpClient;
  3. use Stripe\Stripe;
  4. use Stripe\Error;
  5. use Stripe\Util;
  6. class CurlClient implements ClientInterface
  7. {
  8. private static $instance;
  9. public static function instance()
  10. {
  11. if (!self::$instance) {
  12. self::$instance = new self();
  13. }
  14. return self::$instance;
  15. }
  16. protected $defaultOptions;
  17. /**
  18. * CurlClient constructor.
  19. *
  20. * Pass in a callable to $defaultOptions that returns an array of CURLOPT_* values to start
  21. * off a request with, or an flat array with the same format used by curl_setopt_array() to
  22. * provide a static set of options. Note that many options are overridden later in the request
  23. * call, including timeouts, which can be set via setTimeout() and setConnectTimeout().
  24. *
  25. * Note that request() will silently ignore a non-callable, non-array $defaultOptions, and will
  26. * throw an exception if $defaultOptions returns a non-array value.
  27. *
  28. * @param array|callable|null $defaultOptions
  29. */
  30. public function __construct($defaultOptions = null)
  31. {
  32. $this->defaultOptions = $defaultOptions;
  33. }
  34. public function getDefaultOptions()
  35. {
  36. return $this->defaultOptions;
  37. }
  38. // USER DEFINED TIMEOUTS
  39. const DEFAULT_TIMEOUT = 80;
  40. const DEFAULT_CONNECT_TIMEOUT = 30;
  41. private $timeout = self::DEFAULT_TIMEOUT;
  42. private $connectTimeout = self::DEFAULT_CONNECT_TIMEOUT;
  43. public function setTimeout($seconds)
  44. {
  45. $this->timeout = (int) max($seconds, 0);
  46. return $this;
  47. }
  48. public function setConnectTimeout($seconds)
  49. {
  50. $this->connectTimeout = (int) max($seconds, 0);
  51. return $this;
  52. }
  53. public function getTimeout()
  54. {
  55. return $this->timeout;
  56. }
  57. public function getConnectTimeout()
  58. {
  59. return $this->connectTimeout;
  60. }
  61. // END OF USER DEFINED TIMEOUTS
  62. public function request($method, $absUrl, $headers, $params, $hasFile)
  63. {
  64. $curl = curl_init();
  65. $method = strtolower($method);
  66. $opts = array();
  67. if (is_callable($this->defaultOptions)) { // call defaultOptions callback, set options to return value
  68. $opts = call_user_func_array($this->defaultOptions, func_get_args());
  69. if (!is_array($opts)) {
  70. throw new Error\Api("Non-array value returned by defaultOptions CurlClient callback");
  71. }
  72. } elseif (is_array($this->defaultOptions)) { // set default curlopts from array
  73. $opts = $this->defaultOptions;
  74. }
  75. if ($method == 'get') {
  76. if ($hasFile) {
  77. throw new Error\Api(
  78. "Issuing a GET request with a file parameter"
  79. );
  80. }
  81. $opts[CURLOPT_HTTPGET] = 1;
  82. if (count($params) > 0) {
  83. $encoded = self::encode($params);
  84. $absUrl = "$absUrl?$encoded";
  85. }
  86. } elseif ($method == 'post') {
  87. $opts[CURLOPT_POST] = 1;
  88. $opts[CURLOPT_POSTFIELDS] = $hasFile ? $params : self::encode($params);
  89. } elseif ($method == 'delete') {
  90. $opts[CURLOPT_CUSTOMREQUEST] = 'DELETE';
  91. if (count($params) > 0) {
  92. $encoded = self::encode($params);
  93. $absUrl = "$absUrl?$encoded";
  94. }
  95. } else {
  96. throw new Error\Api("Unrecognized method $method");
  97. }
  98. // Create a callback to capture HTTP headers for the response
  99. $rheaders = array();
  100. $headerCallback = function ($curl, $header_line) use (&$rheaders) {
  101. // Ignore the HTTP request line (HTTP/1.1 200 OK)
  102. if (strpos($header_line, ":") === false) {
  103. return strlen($header_line);
  104. }
  105. list($key, $value) = explode(":", trim($header_line), 2);
  106. $rheaders[trim($key)] = trim($value);
  107. return strlen($header_line);
  108. };
  109. // By default for large request body sizes (> 1024 bytes), cURL will
  110. // send a request without a body and with a `Expect: 100-continue`
  111. // header, which gives the server a chance to respond with an error
  112. // status code in cases where one can be determined right away (say
  113. // on an authentication problem for example), and saves the "large"
  114. // request body from being ever sent.
  115. //
  116. // Unfortunately, the bindings don't currently correctly handle the
  117. // success case (in which the server sends back a 100 CONTINUE), so
  118. // we'll error under that condition. To compensate for that problem
  119. // for the time being, override cURL's behavior by simply always
  120. // sending an empty `Expect:` header.
  121. array_push($headers, 'Expect: ');
  122. $absUrl = Util\Util::utf8($absUrl);
  123. $opts[CURLOPT_URL] = $absUrl;
  124. $opts[CURLOPT_RETURNTRANSFER] = true;
  125. $opts[CURLOPT_CONNECTTIMEOUT] = $this->connectTimeout;
  126. $opts[CURLOPT_TIMEOUT] = $this->timeout;
  127. $opts[CURLOPT_HEADERFUNCTION] = $headerCallback;
  128. $opts[CURLOPT_HTTPHEADER] = $headers;
  129. if (!Stripe::$verifySslCerts) {
  130. $opts[CURLOPT_SSL_VERIFYPEER] = false;
  131. }
  132. // @codingStandardsIgnoreStart
  133. // PSR2 requires all constants be upper case. Sadly, the CURL_SSLVERSION
  134. // constants to not abide by those rules.
  135. //
  136. // Explicitly set a TLS version for cURL to use now that we're starting
  137. // to block 1.0 and 1.1 requests.
  138. //
  139. // If users are on OpenSSL >= 1.0.1, we know that they support TLS 1.2,
  140. // so set that explicitly because on some older Linux distros, clients may
  141. // default to TLS 1.0 even when they have TLS 1.2 available.
  142. //
  143. // For users on much older versions of OpenSSL, set a valid range of
  144. // TLS 1.0 to 1.2 (CURL_SSLVERSION_TLSv1). Note that this may result in
  145. // their requests being blocked unless they're specially flagged into
  146. // being able to use an old TLS version.
  147. //
  148. // Note: The int on the right is pulled from the source of OpenSSL 1.0.1a.
  149. if (OPENSSL_VERSION_NUMBER >= 0x1000100f) {
  150. if (!defined('CURL_SSLVERSION_TLSv1_2')) {
  151. // Note the value 6 comes from its position in the enum that
  152. // defines it in cURL's source code.
  153. define('CURL_SSLVERSION_TLSv1_2', 6); // constant not defined in PHP < 5.5
  154. }
  155. $opts[CURLOPT_SSLVERSION] = CURL_SSLVERSION_TLSv1_2;
  156. } else {
  157. if (!defined('CURL_SSLVERSION_TLSv1')) {
  158. define('CURL_SSLVERSION_TLSv1', 1); // constant not defined in PHP < 5.5
  159. }
  160. $opts[CURLOPT_SSLVERSION] = CURL_SSLVERSION_TLSv1;
  161. }
  162. // @codingStandardsIgnoreEnd
  163. curl_setopt_array($curl, $opts);
  164. $rbody = curl_exec($curl);
  165. if (!defined('CURLE_SSL_CACERT_BADFILE')) {
  166. define('CURLE_SSL_CACERT_BADFILE', 77); // constant not defined in PHP
  167. }
  168. $errno = curl_errno($curl);
  169. if ($errno == CURLE_SSL_CACERT ||
  170. $errno == CURLE_SSL_PEER_CERTIFICATE ||
  171. $errno == CURLE_SSL_CACERT_BADFILE
  172. ) {
  173. array_push(
  174. $headers,
  175. 'X-Stripe-Client-Info: {"ca":"using Stripe-supplied CA bundle"}'
  176. );
  177. $cert = self::caBundle();
  178. curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
  179. curl_setopt($curl, CURLOPT_CAINFO, $cert);
  180. $rbody = curl_exec($curl);
  181. }
  182. if ($rbody === false) {
  183. $errno = curl_errno($curl);
  184. $message = curl_error($curl);
  185. curl_close($curl);
  186. $this->handleCurlError($absUrl, $errno, $message);
  187. }
  188. $rcode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
  189. curl_close($curl);
  190. return array($rbody, $rcode, $rheaders);
  191. }
  192. /**
  193. * @param number $errno
  194. * @param string $message
  195. * @throws Error\ApiConnection
  196. */
  197. private function handleCurlError($url, $errno, $message)
  198. {
  199. switch ($errno) {
  200. case CURLE_COULDNT_CONNECT:
  201. case CURLE_COULDNT_RESOLVE_HOST:
  202. case CURLE_OPERATION_TIMEOUTED:
  203. $msg = "Could not connect to Stripe ($url). Please check your "
  204. . "internet connection and try again. If this problem persists, "
  205. . "you should check Stripe's service status at "
  206. . "https://twitter.com/stripestatus, or";
  207. break;
  208. case CURLE_SSL_CACERT:
  209. case CURLE_SSL_PEER_CERTIFICATE:
  210. $msg = "Could not verify Stripe's SSL certificate. Please make sure "
  211. . "that your network is not intercepting certificates. "
  212. . "(Try going to $url in your browser.) "
  213. . "If this problem persists,";
  214. break;
  215. default:
  216. $msg = "Unexpected error communicating with Stripe. "
  217. . "If this problem persists,";
  218. }
  219. $msg .= " let us know at support@stripe.com.";
  220. $msg .= "\n\n(Network error [errno $errno]: $message)";
  221. throw new Error\ApiConnection($msg);
  222. }
  223. private static function caBundle()
  224. {
  225. return dirname(__FILE__) . '/../../data/ca-certificates.crt';
  226. }
  227. /**
  228. * @param array $arr An map of param keys to values.
  229. * @param string|null $prefix
  230. *
  231. * Only public for testability, should not be called outside of CurlClient
  232. *
  233. * @return string A querystring, essentially.
  234. */
  235. public static function encode($arr, $prefix = null)
  236. {
  237. if (!is_array($arr)) {
  238. return $arr;
  239. }
  240. $r = array();
  241. foreach ($arr as $k => $v) {
  242. if (is_null($v)) {
  243. continue;
  244. }
  245. if ($prefix) {
  246. if ($k !== null && (!is_int($k) || is_array($v))) {
  247. $k = $prefix."[".$k."]";
  248. } else {
  249. $k = $prefix."[]";
  250. }
  251. }
  252. if (is_array($v)) {
  253. $enc = self::encode($v, $k);
  254. if ($enc) {
  255. $r[] = $enc;
  256. }
  257. } else {
  258. $r[] = urlencode($k)."=".urlencode($v);
  259. }
  260. }
  261. return implode("&", $r);
  262. }
  263. }