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
01:08
created

TwitterOAuth::sleepIfNeeded()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

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