CurlClient.php 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184
  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. public function request($method, $absUrl, $headers, $params, $hasFile)
  17. {
  18. $curl = curl_init();
  19. $method = strtolower($method);
  20. $opts = array();
  21. if ($method == 'get') {
  22. if ($hasFile) {
  23. throw new Error\Api(
  24. "Issuing a GET request with a file parameter"
  25. );
  26. }
  27. $opts[CURLOPT_HTTPGET] = 1;
  28. if (count($params) > 0) {
  29. $encoded = self::encode($params);
  30. $absUrl = "$absUrl?$encoded";
  31. }
  32. } elseif ($method == 'post') {
  33. $opts[CURLOPT_POST] = 1;
  34. $opts[CURLOPT_POSTFIELDS] = $hasFile ? $params : self::encode($params);
  35. } elseif ($method == 'delete') {
  36. $opts[CURLOPT_CUSTOMREQUEST] = 'DELETE';
  37. if (count($params) > 0) {
  38. $encoded = self::encode($params);
  39. $absUrl = "$absUrl?$encoded";
  40. }
  41. } else {
  42. throw new Error\Api("Unrecognized method $method");
  43. }
  44. // Create a callback to capture HTTP headers for the response
  45. $rheaders = array();
  46. $headerCallback = function ($curl, $header_line) use (&$rheaders) {
  47. // Ignore the HTTP request line (HTTP/1.1 200 OK)
  48. if (strpos($header_line, ":") === false) {
  49. return strlen($header_line);
  50. }
  51. list($key, $value) = explode(":", trim($header_line), 2);
  52. $rheaders[trim($key)] = trim($value);
  53. return strlen($header_line);
  54. };
  55. $absUrl = Util\Util::utf8($absUrl);
  56. $opts[CURLOPT_URL] = $absUrl;
  57. $opts[CURLOPT_RETURNTRANSFER] = true;
  58. $opts[CURLOPT_CONNECTTIMEOUT] = 30;
  59. $opts[CURLOPT_TIMEOUT] = 80;
  60. $opts[CURLOPT_RETURNTRANSFER] = true;
  61. $opts[CURLOPT_HEADERFUNCTION] = $headerCallback;
  62. $opts[CURLOPT_HTTPHEADER] = $headers;
  63. if (!Stripe::$verifySslCerts) {
  64. $opts[CURLOPT_SSL_VERIFYPEER] = false;
  65. }
  66. curl_setopt_array($curl, $opts);
  67. $rbody = curl_exec($curl);
  68. if (!defined('CURLE_SSL_CACERT_BADFILE')) {
  69. define('CURLE_SSL_CACERT_BADFILE', 77); // constant not defined in PHP
  70. }
  71. $errno = curl_errno($curl);
  72. if ($errno == CURLE_SSL_CACERT ||
  73. $errno == CURLE_SSL_PEER_CERTIFICATE ||
  74. $errno == CURLE_SSL_CACERT_BADFILE
  75. ) {
  76. array_push(
  77. $headers,
  78. 'X-Stripe-Client-Info: {"ca":"using Stripe-supplied CA bundle"}'
  79. );
  80. $cert = self::caBundle();
  81. curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
  82. curl_setopt($curl, CURLOPT_CAINFO, $cert);
  83. $rbody = curl_exec($curl);
  84. }
  85. if ($rbody === false) {
  86. $errno = curl_errno($curl);
  87. $message = curl_error($curl);
  88. curl_close($curl);
  89. $this->handleCurlError($absUrl, $errno, $message);
  90. }
  91. $rcode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
  92. curl_close($curl);
  93. return array($rbody, $rcode, $rheaders);
  94. }
  95. /**
  96. * @param number $errno
  97. * @param string $message
  98. * @throws Error\ApiConnection
  99. */
  100. private function handleCurlError($url, $errno, $message)
  101. {
  102. switch ($errno) {
  103. case CURLE_COULDNT_CONNECT:
  104. case CURLE_COULDNT_RESOLVE_HOST:
  105. case CURLE_OPERATION_TIMEOUTED:
  106. $msg = "Could not connect to Stripe ($url). Please check your "
  107. . "internet connection and try again. If this problem persists, "
  108. . "you should check Stripe's service status at "
  109. . "https://twitter.com/stripestatus, or";
  110. break;
  111. case CURLE_SSL_CACERT:
  112. case CURLE_SSL_PEER_CERTIFICATE:
  113. $msg = "Could not verify Stripe's SSL certificate. Please make sure "
  114. . "that your network is not intercepting certificates. "
  115. . "(Try going to $url in your browser.) "
  116. . "If this problem persists,";
  117. break;
  118. default:
  119. $msg = "Unexpected error communicating with Stripe. "
  120. . "If this problem persists,";
  121. }
  122. $msg .= " let us know at support@stripe.com.";
  123. $msg .= "\n\n(Network error [errno $errno]: $message)";
  124. throw new Error\ApiConnection($msg);
  125. }
  126. private static function caBundle()
  127. {
  128. return dirname(__FILE__) . '/../../data/ca-certificates.crt';
  129. }
  130. /**
  131. * @param array $arr An map of param keys to values.
  132. * @param string|null $prefix
  133. *
  134. * Only public for testability, should not be called outside of CurlClient
  135. *
  136. * @return string A querystring, essentially.
  137. */
  138. public static function encode($arr, $prefix = null)
  139. {
  140. if (!is_array($arr)) {
  141. return $arr;
  142. }
  143. $r = array();
  144. foreach ($arr as $k => $v) {
  145. if (is_null($v)) {
  146. continue;
  147. }
  148. if ($prefix && $k && !is_int($k)) {
  149. $k = $prefix."[".$k."]";
  150. } elseif ($prefix) {
  151. $k = $prefix."[]";
  152. }
  153. if (is_array($v)) {
  154. $enc = self::encode($v, $k);
  155. if ($enc) {
  156. $r[] = $enc;
  157. }
  158. } else {
  159. $r[] = urlencode($k)."=".urlencode($v);
  160. }
  161. }
  162. return implode("&", $r);
  163. }
  164. }