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); } }