Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/phabricator
Path: blob/master/externals/stripe-php/lib/Stripe/ApiRequestor.php
12256 views
1
<?php
2
3
class Stripe_ApiRequestor
4
{
5
/**
6
* @var string $apiKey The API key that's to be used to make requests.
7
*/
8
public $apiKey;
9
10
private static $_preFlight;
11
12
private static function blacklistedCerts()
13
{
14
return array(
15
'05c0b3643694470a888c6e7feb5c9e24e823dc53',
16
'5b7dc7fbc98d78bf76d4d4fa6f597a0c901fad5c',
17
);
18
}
19
20
public function __construct($apiKey=null)
21
{
22
$this->_apiKey = $apiKey;
23
}
24
25
/**
26
* @param string $url The path to the API endpoint.
27
*
28
* @returns string The full path.
29
*/
30
public static function apiUrl($url='')
31
{
32
$apiBase = Stripe::$apiBase;
33
return "$apiBase$url";
34
}
35
36
/**
37
* @param string|mixed $value A string to UTF8-encode.
38
*
39
* @returns string|mixed The UTF8-encoded string, or the object passed in if
40
* it wasn't a string.
41
*/
42
public static function utf8($value)
43
{
44
if (is_string($value)
45
&& mb_detect_encoding($value, "UTF-8", TRUE) != "UTF-8") {
46
return utf8_encode($value);
47
} else {
48
return $value;
49
}
50
}
51
52
private static function _encodeObjects($d)
53
{
54
if ($d instanceof Stripe_ApiResource) {
55
return self::utf8($d->id);
56
} else if ($d === true) {
57
return 'true';
58
} else if ($d === false) {
59
return 'false';
60
} else if (is_array($d)) {
61
$res = array();
62
foreach ($d as $k => $v)
63
$res[$k] = self::_encodeObjects($v);
64
return $res;
65
} else {
66
return self::utf8($d);
67
}
68
}
69
70
/**
71
* @param array $arr An map of param keys to values.
72
* @param string|null $prefix (It doesn't look like we ever use $prefix...)
73
*
74
* @returns string A querystring, essentially.
75
*/
76
public static function encode($arr, $prefix=null)
77
{
78
if (!is_array($arr))
79
return $arr;
80
81
$r = array();
82
foreach ($arr as $k => $v) {
83
if (is_null($v))
84
continue;
85
86
if ($prefix && $k && !is_int($k))
87
$k = $prefix."[".$k."]";
88
else if ($prefix)
89
$k = $prefix."[]";
90
91
if (is_array($v)) {
92
$r[] = self::encode($v, $k, true);
93
} else {
94
$r[] = urlencode($k)."=".urlencode($v);
95
}
96
}
97
98
return implode("&", $r);
99
}
100
101
/**
102
* @param string $method
103
* @param string $url
104
* @param array|null $params
105
*
106
* @return array An array whose first element is the response and second
107
* element is the API key used to make the request.
108
*/
109
public function request($method, $url, $params=null)
110
{
111
if (!$params)
112
$params = array();
113
list($rbody, $rcode, $myApiKey) =
114
$this->_requestRaw($method, $url, $params);
115
$resp = $this->_interpretResponse($rbody, $rcode);
116
return array($resp, $myApiKey);
117
}
118
119
120
/**
121
* @param string $rbody A JSON string.
122
* @param int $rcode
123
* @param array $resp
124
*
125
* @throws Stripe_InvalidRequestError if the error is caused by the user.
126
* @throws Stripe_AuthenticationError if the error is caused by a lack of
127
* permissions.
128
* @throws Stripe_CardError if the error is the error code is 402 (payment
129
* required)
130
* @throws Stripe_ApiError otherwise.
131
*/
132
public function handleApiError($rbody, $rcode, $resp)
133
{
134
if (!is_array($resp) || !isset($resp['error'])) {
135
$msg = "Invalid response object from API: $rbody "
136
."(HTTP response code was $rcode)";
137
throw new Stripe_ApiError($msg, $rcode, $rbody, $resp);
138
}
139
140
$error = $resp['error'];
141
$msg = isset($error['message']) ? $error['message'] : null;
142
$param = isset($error['param']) ? $error['param'] : null;
143
$code = isset($error['code']) ? $error['code'] : null;
144
145
switch ($rcode) {
146
case 400:
147
if ($code == 'rate_limit') {
148
throw new Stripe_RateLimitError(
149
$msg, $param, $rcode, $rbody, $resp
150
);
151
}
152
case 404:
153
throw new Stripe_InvalidRequestError(
154
$msg, $param, $rcode, $rbody, $resp
155
);
156
case 401:
157
throw new Stripe_AuthenticationError($msg, $rcode, $rbody, $resp);
158
case 402:
159
throw new Stripe_CardError($msg, $param, $code, $rcode, $rbody, $resp);
160
default:
161
throw new Stripe_ApiError($msg, $rcode, $rbody, $resp);
162
}
163
}
164
165
private function _requestRaw($method, $url, $params)
166
{
167
$myApiKey = $this->_apiKey;
168
if (!$myApiKey)
169
$myApiKey = Stripe::$apiKey;
170
171
if (!$myApiKey) {
172
$msg = 'No API key provided. (HINT: set your API key using '
173
. '"Stripe::setApiKey(<API-KEY>)". You can generate API keys from '
174
. 'the Stripe web interface. See https://stripe.com/api for '
175
. 'details, or email [email protected] if you have any questions.';
176
throw new Stripe_AuthenticationError($msg);
177
}
178
179
$absUrl = $this->apiUrl($url);
180
$params = self::_encodeObjects($params);
181
$langVersion = phpversion();
182
$uname = php_uname();
183
$ua = array('bindings_version' => Stripe::VERSION,
184
'lang' => 'php',
185
'lang_version' => $langVersion,
186
'publisher' => 'stripe',
187
'uname' => $uname);
188
$headers = array('X-Stripe-Client-User-Agent: ' . json_encode($ua),
189
'User-Agent: Stripe/v1 PhpBindings/' . Stripe::VERSION,
190
'Authorization: Bearer ' . $myApiKey);
191
if (Stripe::$apiVersion)
192
$headers[] = 'Stripe-Version: ' . Stripe::$apiVersion;
193
list($rbody, $rcode) = $this->_curlRequest(
194
$method,
195
$absUrl,
196
$headers,
197
$params
198
);
199
return array($rbody, $rcode, $myApiKey);
200
}
201
202
private function _interpretResponse($rbody, $rcode)
203
{
204
try {
205
$resp = json_decode($rbody, true);
206
} catch (Exception $e) {
207
$msg = "Invalid response body from API: $rbody "
208
. "(HTTP response code was $rcode)";
209
throw new Stripe_ApiError($msg, $rcode, $rbody);
210
}
211
212
if ($rcode < 200 || $rcode >= 300) {
213
$this->handleApiError($rbody, $rcode, $resp);
214
}
215
return $resp;
216
}
217
218
private function _curlRequest($method, $absUrl, $headers, $params)
219
{
220
221
if (!self::$_preFlight) {
222
self::$_preFlight = $this->checkSslCert($this->apiUrl());
223
}
224
225
$curl = curl_init();
226
$method = strtolower($method);
227
$opts = array();
228
if ($method == 'get') {
229
$opts[CURLOPT_HTTPGET] = 1;
230
if (count($params) > 0) {
231
$encoded = self::encode($params);
232
$absUrl = "$absUrl?$encoded";
233
}
234
} else if ($method == 'post') {
235
$opts[CURLOPT_POST] = 1;
236
$opts[CURLOPT_POSTFIELDS] = self::encode($params);
237
} else if ($method == 'delete') {
238
$opts[CURLOPT_CUSTOMREQUEST] = 'DELETE';
239
if (count($params) > 0) {
240
$encoded = self::encode($params);
241
$absUrl = "$absUrl?$encoded";
242
}
243
} else {
244
throw new Stripe_ApiError("Unrecognized method $method");
245
}
246
247
$absUrl = self::utf8($absUrl);
248
$opts[CURLOPT_URL] = $absUrl;
249
$opts[CURLOPT_RETURNTRANSFER] = true;
250
$opts[CURLOPT_CONNECTTIMEOUT] = 30;
251
$opts[CURLOPT_TIMEOUT] = 80;
252
$opts[CURLOPT_RETURNTRANSFER] = true;
253
$opts[CURLOPT_HTTPHEADER] = $headers;
254
if (!Stripe::$verifySslCerts)
255
$opts[CURLOPT_SSL_VERIFYPEER] = false;
256
257
curl_setopt_array($curl, $opts);
258
$rbody = curl_exec($curl);
259
260
if (!defined('CURLE_SSL_CACERT_BADFILE')) {
261
define('CURLE_SSL_CACERT_BADFILE', 77); // constant not defined in PHP
262
}
263
264
$errno = curl_errno($curl);
265
if ($errno == CURLE_SSL_CACERT ||
266
$errno == CURLE_SSL_PEER_CERTIFICATE ||
267
$errno == CURLE_SSL_CACERT_BADFILE) {
268
array_push(
269
$headers,
270
'X-Stripe-Client-Info: {"ca":"using Stripe-supplied CA bundle"}'
271
);
272
$cert = $this->caBundle();
273
curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
274
curl_setopt($curl, CURLOPT_CAINFO, $cert);
275
$rbody = curl_exec($curl);
276
}
277
278
if ($rbody === false) {
279
$errno = curl_errno($curl);
280
$message = curl_error($curl);
281
curl_close($curl);
282
$this->handleCurlError($errno, $message);
283
}
284
285
$rcode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
286
curl_close($curl);
287
return array($rbody, $rcode);
288
}
289
290
/**
291
* @param number $errno
292
* @param string $message
293
* @throws Stripe_ApiConnectionError
294
*/
295
public function handleCurlError($errno, $message)
296
{
297
$apiBase = Stripe::$apiBase;
298
switch ($errno) {
299
case CURLE_COULDNT_CONNECT:
300
case CURLE_COULDNT_RESOLVE_HOST:
301
case CURLE_OPERATION_TIMEOUTED:
302
$msg = "Could not connect to Stripe ($apiBase). Please check your "
303
. "internet connection and try again. If this problem persists, "
304
. "you should check Stripe's service status at "
305
. "https://twitter.com/stripestatus, or";
306
break;
307
case CURLE_SSL_CACERT:
308
case CURLE_SSL_PEER_CERTIFICATE:
309
$msg = "Could not verify Stripe's SSL certificate. Please make sure "
310
. "that your network is not intercepting certificates. "
311
. "(Try going to $apiBase in your browser.) "
312
. "If this problem persists,";
313
break;
314
default:
315
$msg = "Unexpected error communicating with Stripe. "
316
. "If this problem persists,";
317
}
318
$msg .= " let us know at [email protected].";
319
320
$msg .= "\n\n(Network error [errno $errno]: $message)";
321
throw new Stripe_ApiConnectionError($msg);
322
}
323
324
/**
325
* Preflight the SSL certificate presented by the backend. This isn't 100%
326
* bulletproof, in that we're not actually validating the transport used to
327
* communicate with Stripe, merely that the first attempt to does not use a
328
* revoked certificate.
329
*
330
* Unfortunately the interface to OpenSSL doesn't make it easy to check the
331
* certificate before sending potentially sensitive data on the wire. This
332
* approach raises the bar for an attacker significantly.
333
*/
334
private function checkSslCert($url)
335
{
336
if (version_compare(PHP_VERSION, '5.3.0', '<')) {
337
error_log(
338
'Warning: This version of PHP is too old to check SSL certificates '.
339
'correctly. Stripe cannot guarantee that the server has a '.
340
'certificate which is not blacklisted'
341
);
342
return true;
343
}
344
345
if (strpos(PHP_VERSION, 'hiphop') !== false) {
346
error_log(
347
'Warning: HHVM does not support Stripe\'s SSL certificate '.
348
'verification. (See http://docs.hhvm.com/manual/en/context.ssl.php) '.
349
'Stripe cannot guarantee that the server has a certificate which is '.
350
'not blacklisted'
351
);
352
return true;
353
}
354
355
$url = parse_url($url);
356
$port = isset($url["port"]) ? $url["port"] : 443;
357
$url = "ssl://{$url["host"]}:{$port}";
358
359
$sslContext = stream_context_create(
360
array('ssl' => array(
361
'capture_peer_cert' => true,
362
'verify_peer' => true,
363
'cafile' => $this->caBundle(),
364
))
365
);
366
$result = stream_socket_client(
367
$url, $errno, $errstr, 30, STREAM_CLIENT_CONNECT, $sslContext
368
);
369
if ($errno !== 0) {
370
$apiBase = Stripe::$apiBase;
371
throw new Stripe_ApiConnectionError(
372
'Could not connect to Stripe (' . $apiBase . '). Please check your '.
373
'internet connection and try again. If this problem persists, '.
374
'you should check Stripe\'s service status at '.
375
'https://twitter.com/stripestatus. Reason was: '.$errstr
376
);
377
}
378
379
$params = stream_context_get_params($result);
380
381
$cert = $params['options']['ssl']['peer_certificate'];
382
383
openssl_x509_export($cert, $pemCert);
384
385
if (self::isBlackListed($pemCert)) {
386
throw new Stripe_ApiConnectionError(
387
'Invalid server certificate. You tried to connect to a server that '.
388
'has a revoked SSL certificate, which means we cannot securely send '.
389
'data to that server. Please email [email protected] if you need '.
390
'help connecting to the correct API server.'
391
);
392
}
393
394
return true;
395
}
396
397
/* Checks if a valid PEM encoded certificate is blacklisted
398
* @return boolean
399
*/
400
public static function isBlackListed($certificate)
401
{
402
$certificate = trim($certificate);
403
$lines = explode("\n", $certificate);
404
405
// Kludgily remove the PEM padding
406
array_shift($lines); array_pop($lines);
407
408
$derCert = base64_decode(implode("", $lines));
409
$fingerprint = sha1($derCert);
410
return in_array($fingerprint, self::blacklistedCerts());
411
}
412
413
private function caBundle()
414
{
415
return dirname(__FILE__) . '/../data/ca-certificates.crt';
416
}
417
}
418
419