GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Completed
Pull Request — master (#588)
by
unknown
02:43
created

TwitterOAuth::curlOptions()   B

Complexity

Conditions 4
Paths 8

Size

Total Lines 31
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 31
rs 8.5806
c 0
b 0
f 0
cc 4
eloc 20
nc 8
nop 0
1
<?php
2
/**
3
 * The most popular PHP library for use with the Twitter OAuth REST API.
4
 *
5
 * @license MIT
6
 */
7
namespace Abraham\TwitterOAuth;
8
9
use Abraham\TwitterOAuth\Util\JsonDecoder;
10
11
/**
12
 * TwitterOAuth class for interacting with the Twitter API.
13
 *
14
 * @author Abraham Williams <[email protected]>
15
 */
16
class TwitterOAuth extends Config
17
{
18
    const API_VERSION = '1.1';
19
    const API_HOST = 'https://api.twitter.com';
20
    const UPLOAD_HOST = 'https://upload.twitter.com';
21
22
    /** @var Response details about the result of the last request */
23
    private $response;
24
    /** @var string|null Application bearer token */
25
    private $bearer;
26
    /** @var Consumer Twitter application details */
27
    private $consumer;
28
    /** @var Token|null User access token details */
29
    private $token;
30
    /** @var HmacSha1 OAuth 1 signature type used by Twitter */
31
    private $signatureMethod;
32
    /** @var int Number of attempts we made for the request */
33
    private $attempts = 0;
34
35
    /**
36
     * Constructor
37
     *
38
     * @param string      $consumerKey      The Application Consumer Key
39
     * @param string      $consumerSecret   The Application Consumer Secret
40
     * @param string|null $oauthToken       The Client Token (optional)
41
     * @param string|null $oauthTokenSecret The Client Token Secret (optional)
42
     */
43
    public function __construct($consumerKey, $consumerSecret, $oauthToken = null, $oauthTokenSecret = null)
44
    {
45
        $this->resetLastResponse();
46
        $this->signatureMethod = new HmacSha1();
47
        $this->consumer = new Consumer($consumerKey, $consumerSecret);
48
        if (!empty($oauthToken) && !empty($oauthTokenSecret)) {
49
            $this->token = new Token($oauthToken, $oauthTokenSecret);
50
        }
51
        if (empty($oauthToken) && !empty($oauthTokenSecret)) {
52
            $this->bearer = $oauthTokenSecret;
53
        }
54
    }
55
56
    /**
57
     * @param string $oauthToken
58
     * @param string $oauthTokenSecret
59
     */
60
    public function setOauthToken($oauthToken, $oauthTokenSecret)
61
    {
62
        $this->token = new Token($oauthToken, $oauthTokenSecret);
63
    }
64
65
    /**
66
     * @return string|null
67
     */
68
    public function getLastApiPath()
69
    {
70
        return $this->response->getApiPath();
71
    }
72
73
    /**
74
     * @return int
75
     */
76
    public function getLastHttpCode()
77
    {
78
        return $this->response->getHttpCode();
79
    }
80
81
    /**
82
     * @return array
83
     */
84
    public function getLastXHeaders()
85
    {
86
        return $this->response->getXHeaders();
87
    }
88
89
    /**
90
     * @return array|object|null
91
     */
92
    public function getLastBody()
93
    {
94
        return $this->response->getBody();
95
    }
96
97
    /**
98
     * Resets the last response cache.
99
     */
100
    public function resetLastResponse()
101
    {
102
        $this->response = new Response();
103
    }
104
105
    /**
106
     * Resets the attempts number.
107
     */
108
    public function resetAttemptsNumber()
109
    {
110
        $this->attempts = 0;
111
    }
112
113
    /**
114
     * Make URLs for user browser navigation.
115
     *
116
     * @param string $path
117
     * @param array  $parameters
118
     *
119
     * @return string
120
     */
121
    public function url($path, array $parameters)
122
    {
123
        $this->resetLastResponse();
124
        $this->response->setApiPath($path);
125
        $query = http_build_query($parameters);
126
        return sprintf('%s/%s?%s', self::API_HOST, $path, $query);
127
    }
128
129
    /**
130
     * Make /oauth/* requests to the API.
131
     *
132
     * @param string $path
133
     * @param array  $parameters
134
     *
135
     * @return array
136
     * @throws TwitterOAuthException
137
     */
138
    public function oauth($path, array $parameters = [])
139
    {
140
        $response = [];
141
        $this->resetLastResponse();
142
        $this->response->setApiPath($path);
143
        $url = sprintf('%s/%s', self::API_HOST, $path);
144
        $result = $this->oAuthRequest($url, 'POST', $parameters);
145
146
        if ($this->getLastHttpCode() != 200) {
147
            throw new TwitterOAuthException($result);
148
        }
149
150
        parse_str($result, $response);
151
        $this->response->setBody($response);
0 ignored issues
show
Bug introduced by
It seems like $response can also be of type null; however, Abraham\TwitterOAuth\Response::setBody() does only seem to accept array|object, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
152
153
        return $response;
154
    }
155
156
    /**
157
     * Make /oauth2/* requests to the API.
158
     *
159
     * @param string $path
160
     * @param array  $parameters
161
     *
162
     * @return array|object
163
     */
164
    public function oauth2($path, array $parameters = [])
165
    {
166
        $method = 'POST';
167
        $this->resetLastResponse();
168
        $this->response->setApiPath($path);
169
        $url = sprintf('%s/%s', self::API_HOST, $path);
170
        $request = Request::fromConsumerAndToken($this->consumer, $this->token, $method, $url, $parameters);
171
        $authorization = 'Authorization: Basic ' . $this->encodeAppAuthorization($this->consumer);
172
        $result = $this->request($request->getNormalizedHttpUrl(), $method, $authorization, $parameters);
173
        $response = JsonDecoder::decode($result, $this->decodeJsonAsArray);
174
        $this->response->setBody($response);
175
        return $response;
176
    }
177
178
    /**
179
     * Make GET requests to the API.
180
     *
181
     * @param string $path
182
     * @param array  $parameters
183
     *
184
     * @return array|object
185
     */
186
    public function get($path, array $parameters = [])
187
    {
188
        return $this->http('GET', self::API_HOST, $path, $parameters);
189
    }
190
191
    /**
192
     * Make POST requests to the API.
193
     *
194
     * @param string $path
195
     * @param array  $parameters
196
     *
197
     * @return array|object
198
     */
199
    public function post($path, array $parameters = [])
200
    {
201
        return $this->http('POST', self::API_HOST, $path, $parameters);
202
    }
203
204
    /**
205
     * Make DELETE requests to the API.
206
     *
207
     * @param string $path
208
     * @param array  $parameters
209
     *
210
     * @return array|object
211
     */
212
    public function delete($path, array $parameters = [])
213
    {
214
        return $this->http('DELETE', self::API_HOST, $path, $parameters);
215
    }
216
217
    /**
218
     * Make PUT requests to the API.
219
     *
220
     * @param string $path
221
     * @param array  $parameters
222
     *
223
     * @return array|object
224
     */
225
    public function put($path, array $parameters = [])
226
    {
227
        return $this->http('PUT', self::API_HOST, $path, $parameters);
228
    }
229
230
    /**
231
     * Upload media to upload.twitter.com.
232
     *
233
     * @param string $path
234
     * @param array  $parameters
235
     * @param boolean  $chunked
236
     *
237
     * @return array|object
238
     */
239
    public function upload($path, array $parameters = [], $chunked = false)
240
    {
241
        if ($chunked) {
242
            return $this->uploadMediaChunked($path, $parameters);
243
        } else {
244
            return $this->uploadMediaNotChunked($path, $parameters);
245
        }
246
    }
247
248
    /**
249
     * Private method to upload media (not chunked) to upload.twitter.com.
250
     *
251
     * @param string $path
252
     * @param array  $parameters
253
     *
254
     * @return array|object
255
     */
256
    private function uploadMediaNotChunked($path, array $parameters)
257
    {
258
        $file = file_get_contents($parameters['media']);
259
        $base = base64_encode($file);
260
        $parameters['media'] = $base;
261
        return $this->http('POST', self::UPLOAD_HOST, $path, $parameters);
262
    }
263
264
    /**
265
     * Private method to upload media (chunked) to upload.twitter.com.
266
     *
267
     * @param string $path
268
     * @param array  $parameters
269
     *
270
     * @return array|object
271
     */
272
    private function uploadMediaChunked($path, array $parameters)
273
    {
274
        $init = $this->http('POST', self::UPLOAD_HOST, $path, $this->mediaInitParameters($parameters));
275
        // Append
276
        $segmentIndex = 0;
277
        $media = fopen($parameters['media'], 'rb');
278
        while (!feof($media)) {
279
            $this->http('POST', self::UPLOAD_HOST, 'media/upload', [
280
                'command' => 'APPEND',
281
                'media_id' => $init->media_id_string,
282
                'segment_index' => $segmentIndex++,
283
                'media_data' => base64_encode(fread($media, $this->chunkSize))
284
            ]);
285
        }
286
        fclose($media);
287
        // Finalize
288
        $finalize = $this->http('POST', self::UPLOAD_HOST, 'media/upload', [
289
            'command' => 'FINALIZE',
290
            'media_id' => $init->media_id_string
291
        ]);
292
        return $finalize;
293
    }
294
295
    /**
296
     * Private method to get params for upload media chunked init.
297
     * Twitter docs: https://dev.twitter.com/rest/reference/post/media/upload-init.html
298
     *
299
     * @param array  $parameters
300
     *
301
     * @return array
302
     */
303
    private function mediaInitParameters(array $parameters)
304
    {
305
        $return = [
306
            'command' => 'INIT',
307
            'media_type' => $parameters['media_type'],
308
            'total_bytes' => filesize($parameters['media'])
309
        ];
310
        if (isset($parameters['additional_owners'])) {
311
            $return['additional_owners'] = $parameters['additional_owners'];
312
        }
313
        if (isset($parameters['media_category'])) {
314
            $return['media_category'] = $parameters['media_category'];
315
        }
316
        return $return;
317
    }
318
319
    /**
320
     * @param string $method
321
     * @param string $host
322
     * @param string $path
323
     * @param array  $parameters
324
     *
325
     * @return array|object
326
     */
327
    private function http($method, $host, $path, array $parameters)
328
    {
329
        $this->resetLastResponse();
330
        $url = sprintf('%s/%s/%s.json', $host, self::API_VERSION, $path);
331
        $this->response->setApiPath($path);
332
333
        do {
334
            // Only sleep on retry, not on initial request
335
            sleep($this->attempts ? 0 : $this->retriesDelay);
336
            $result = $this->oAuthRequest($url, $method, $parameters);
337
            $response = JsonDecoder::decode($result, $this->decodeJsonAsArray);
338
            $this->response->setBody($response);
339
            $this->attempts++;
340
            // Retry up to our $maxRetries number if we get errors greater than 500 (over capacity etc)
341
        } while ($this->attempts <= $this->maxRetries && $this->getLastHttpCode() < 500);
342
343
        $this->resetAttemptsNumber();
344
345
        return $response;
346
    }
347
348
    /**
349
     * Format and sign an OAuth / API request
350
     *
351
     * @param string $url
352
     * @param string $method
353
     * @param array  $parameters
354
     *
355
     * @return string
356
     * @throws TwitterOAuthException
357
     */
358
    private function oAuthRequest($url, $method, array $parameters)
359
    {
360
        $request = Request::fromConsumerAndToken($this->consumer, $this->token, $method, $url, $parameters);
361
        if (array_key_exists('oauth_callback', $parameters)) {
362
            // Twitter doesn't like oauth_callback as a parameter.
363
            unset($parameters['oauth_callback']);
364
        }
365
        if ($this->bearer === null) {
366
            $request->signRequest($this->signatureMethod, $this->consumer, $this->token);
367
            $authorization = $request->toHeader();
368
            if (array_key_exists('oauth_verifier', $parameters)) {
369
                // Twitter doesn't always work with oauth in the body and in the header
370
                // and it's already included in the $authorization header
371
                unset($parameters['oauth_verifier']);
372
            }
373
        } else {
374
            $authorization = 'Authorization: Bearer ' . $this->bearer;
375
        }
376
        return $this->request($request->getNormalizedHttpUrl(), $method, $authorization, $parameters);
377
    }
378
379
    /**
380
     * Set Curl options.
381
     *
382
     * @return array
383
     */
384
    private function curlOptions()
385
    {
386
        $options = [
387
            // CURLOPT_VERBOSE => true,
388
            CURLOPT_CONNECTTIMEOUT => $this->connectionTimeout,
389
            CURLOPT_HEADER => true,
390
            CURLOPT_RETURNTRANSFER => true,
391
            CURLOPT_SSL_VERIFYHOST => 2,
392
            CURLOPT_SSL_VERIFYPEER => true,
393
            CURLOPT_TIMEOUT => $this->timeout,
394
            CURLOPT_USERAGENT => $this->userAgent,
395
        ];
396
397
        if ($this->useCAFile()) {
398
            $options[CURLOPT_CAINFO] = __DIR__ . DIRECTORY_SEPARATOR . 'cacert.pem';
399
        }
400
401
        if ($this->gzipEncoding) {
402
            $options[CURLOPT_ENCODING] = 'gzip';
403
        }
404
405
        if (!empty($this->proxy)) {
406
            $options[CURLOPT_PROXY] = $this->proxy['CURLOPT_PROXY'];
407
            $options[CURLOPT_PROXYUSERPWD] = $this->proxy['CURLOPT_PROXYUSERPWD'];
408
            $options[CURLOPT_PROXYPORT] = $this->proxy['CURLOPT_PROXYPORT'];
409
            $options[CURLOPT_PROXYAUTH] = CURLAUTH_BASIC;
410
            $options[CURLOPT_PROXYTYPE] = CURLPROXY_HTTP;
411
        }
412
413
        return $options;
414
    }
415
416
    /**
417
     * Make an HTTP request
418
     *
419
     * @param string $url
420
     * @param string $method
421
     * @param string $authorization
422
     * @param array $postfields
423
     *
424
     * @return string
425
     * @throws TwitterOAuthException
426
     */
427
    private function request($url, $method, $authorization, array $postfields)
428
    {
429
        $options = $this->curlOptions();
430
        $options[CURLOPT_URL] = $url;
431
        $options[CURLOPT_HTTPHEADER] = ['Accept: application/json', $authorization, 'Expect:'];
432
433
        switch ($method) {
434
            case 'GET':
435
                break;
436
            case 'POST':
437
                $options[CURLOPT_POST] = true;
438
                $options[CURLOPT_POSTFIELDS] = Util::buildHttpQuery($postfields);
439
                break;
440
            case 'DELETE':
441
                $options[CURLOPT_CUSTOMREQUEST] = 'DELETE';
442
                break;
443
            case 'PUT':
444
                $options[CURLOPT_CUSTOMREQUEST] = 'PUT';
445
                break;
446
        }
447
448
        if (in_array($method, ['GET', 'PUT', 'DELETE']) && !empty($postfields)) {
449
            $options[CURLOPT_URL] .= '?' . Util::buildHttpQuery($postfields);
450
        }
451
452
453
        $curlHandle = curl_init();
454
        curl_setopt_array($curlHandle, $options);
455
        $response = curl_exec($curlHandle);
456
457
        // Throw exceptions on cURL errors.
458
        if (curl_errno($curlHandle) > 0) {
459
            throw new TwitterOAuthException(curl_error($curlHandle), curl_errno($curlHandle));
460
        }
461
462
        $this->response->setHttpCode(curl_getinfo($curlHandle, CURLINFO_HTTP_CODE));
463
        $parts = explode("\r\n\r\n", $response);
464
        $responseBody = array_pop($parts);
465
        $responseHeader = array_pop($parts);
466
        $this->response->setHeaders($this->parseHeaders($responseHeader));
467
468
        curl_close($curlHandle);
469
470
        return $responseBody;
471
    }
472
473
    /**
474
     * Get the header info to store.
475
     *
476
     * @param string $header
477
     *
478
     * @return array
479
     */
480
    private function parseHeaders($header)
481
    {
482
        $headers = [];
483
        foreach (explode("\r\n", $header) as $line) {
484
            if (strpos($line, ':') !== false) {
485
                list ($key, $value) = explode(': ', $line);
486
                $key = str_replace('-', '_', strtolower($key));
487
                $headers[$key] = trim($value);
488
            }
489
        }
490
        return $headers;
491
    }
492
493
    /**
494
     * Encode application authorization header with base64.
495
     *
496
     * @param Consumer $consumer
497
     *
498
     * @return string
499
     */
500
    private function encodeAppAuthorization(Consumer $consumer)
501
    {
502
        $key = rawurlencode($consumer->key);
503
        $secret = rawurlencode($consumer->secret);
504
        return base64_encode($key . ':' . $secret);
505
    }
506
507
    /**
508
     * Is the code running from a Phar module.
509
     *
510
     * @return boolean
511
     */
512
    private function pharRunning()
513
    {
514
        return class_exists('Phar') && \Phar::running(false) !== '';
515
    }
516
517
    /**
518
     * Use included CA file instead of OS provided list.
519
     *
520
     * @return boolean
521
     */
522
    private function useCAFile()
523
    {
524
        /* Use CACert file when not in a PHAR file. */
525
        return !$this->pharRunning();
526
    }
527
}
528