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 (#607)
by
unknown
05:14
created

TwitterOAuth::uploadMediaChunked()   B

Complexity

Conditions 3
Paths 3

Size

Total Lines 27
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 27
rs 8.8571
c 0
b 0
f 0
cc 3
eloc 17
nc 3
nop 2
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
        // If there are errors with the file, like gif or video requirements are not met
287
        if (!isset($init->media_id_string)) {
288
            // Return the object with the error from the API just like not chunked method
289
            return $init;
290
        }
291
        // Append
292
        $segmentIndex = 0;
293
        $media = fopen($parameters['media'], 'rb');
294
        while (!feof($media)) {
295
            $this->http('POST', self::UPLOAD_HOST, 'media/upload', [
296
                'command' => 'APPEND',
297
                'media_id' => $init->media_id_string,
298
                'segment_index' => $segmentIndex++,
299
                'media_data' => base64_encode(fread($media, $this->chunkSize))
300
            ]);
301
        }
302
        fclose($media);
303
        // Finalize
304
        $finalize = $this->http('POST', self::UPLOAD_HOST, 'media/upload', [
305
            'command' => 'FINALIZE',
306
            'media_id' => $init->media_id_string
307
        ]);
308
        return $finalize;
309
    }
310
311
    /**
312
     * Private method to get params for upload media chunked init.
313
     * Twitter docs: https://dev.twitter.com/rest/reference/post/media/upload-init.html
314
     *
315
     * @param array  $parameters
316
     *
317
     * @return array
318
     */
319
    private function mediaInitParameters(array $parameters)
320
    {
321
        $return = [
322
            'command' => 'INIT',
323
            'media_type' => $parameters['media_type'],
324
            'total_bytes' => filesize($parameters['media'])
325
        ];
326
        if (isset($parameters['additional_owners'])) {
327
            $return['additional_owners'] = $parameters['additional_owners'];
328
        }
329
        if (isset($parameters['media_category'])) {
330
            $return['media_category'] = $parameters['media_category'];
331
        }
332
        return $return;
333
    }
334
335
    /**
336
     * @param string $method
337
     * @param string $host
338
     * @param string $path
339
     * @param array  $parameters
340
     *
341
     * @return array|object
342
     */
343
    private function http($method, $host, $path, array $parameters)
344
    {
345
        $this->resetLastResponse();
346
        $this->resetAttemptsNumber();
347
        $url = sprintf('%s/%s/%s.json', $host, self::API_VERSION, $path);
348
        $this->response->setApiPath($path);
349
        return $this->makeRequests($url, $method, $parameters);
350
    }
351
352
    /**
353
     *
354
     * Make requests and retry them (if enabled) in case of Twitter's problems.
355
     *
356
     * @param string $method
357
     * @param string $url
358
     * @param string $method
359
     * @param array  $parameters
360
     *
361
     * @return array|object
362
     */
363
    private function makeRequests($url, $method, array $parameters)
364
    {
365
        do {
366
            $this->sleepIfNeeded();
367
            $result = $this->oAuthRequest($url, $method, $parameters);
368
            $response = JsonDecoder::decode($result, $this->decodeJsonAsArray);
369
            $this->response->setBody($response);
370
            $this->attempts++;
371
            // Retry up to our $maxRetries number if we get errors greater than 500 (over capacity etc)
372
        } while ($this->requestsAvailable());
373
374
        return $response;
375
    }
376
377
    /**
378
     * Checks if we have to retry request if API is down.
379
     *
380
     * @return bool
381
     */
382
    private function requestsAvailable()
383
    {
384
        return ($this->maxRetries && ($this->attempts <= $this->maxRetries) && $this->getLastHttpCode() >= 500);
385
    }
386
387
    /**
388
     * Format and sign an OAuth / API request
389
     *
390
     * @param string $url
391
     * @param string $method
392
     * @param array  $parameters
393
     *
394
     * @return string
395
     * @throws TwitterOAuthException
396
     */
397
    private function oAuthRequest($url, $method, array $parameters)
398
    {
399
        $request = Request::fromConsumerAndToken($this->consumer, $this->token, $method, $url, $parameters);
400
        if (array_key_exists('oauth_callback', $parameters)) {
401
            // Twitter doesn't like oauth_callback as a parameter.
402
            unset($parameters['oauth_callback']);
403
        }
404
        if ($this->bearer === null) {
405
            $request->signRequest($this->signatureMethod, $this->consumer, $this->token);
406
            $authorization = $request->toHeader();
407
            if (array_key_exists('oauth_verifier', $parameters)) {
408
                // Twitter doesn't always work with oauth in the body and in the header
409
                // and it's already included in the $authorization header
410
                unset($parameters['oauth_verifier']);
411
            }
412
        } else {
413
            $authorization = 'Authorization: Bearer ' . $this->bearer;
414
        }
415
        return $this->request($request->getNormalizedHttpUrl(), $method, $authorization, $parameters);
416
    }
417
418
    /**
419
     * Set Curl options.
420
     *
421
     * @return array
422
     */
423
    private function curlOptions()
424
    {
425
        $options = [
426
            // CURLOPT_VERBOSE => true,
427
            CURLOPT_CONNECTTIMEOUT => $this->connectionTimeout,
428
            CURLOPT_HEADER => true,
429
            CURLOPT_RETURNTRANSFER => true,
430
            CURLOPT_SSL_VERIFYHOST => 2,
431
            CURLOPT_SSL_VERIFYPEER => true,
432
            CURLOPT_TIMEOUT => $this->timeout,
433
            CURLOPT_USERAGENT => $this->userAgent,
434
        ];
435
436
        if ($this->useCAFile()) {
437
            $options[CURLOPT_CAINFO] = __DIR__ . DIRECTORY_SEPARATOR . 'cacert.pem';
438
        }
439
440
        if ($this->gzipEncoding) {
441
            $options[CURLOPT_ENCODING] = 'gzip';
442
        }
443
444
        if (!empty($this->proxy)) {
445
            $options[CURLOPT_PROXY] = $this->proxy['CURLOPT_PROXY'];
446
            $options[CURLOPT_PROXYUSERPWD] = $this->proxy['CURLOPT_PROXYUSERPWD'];
447
            $options[CURLOPT_PROXYPORT] = $this->proxy['CURLOPT_PROXYPORT'];
448
            $options[CURLOPT_PROXYAUTH] = CURLAUTH_BASIC;
449
            $options[CURLOPT_PROXYTYPE] = CURLPROXY_HTTP;
450
        }
451
452
        return $options;
453
    }
454
455
    /**
456
     * Make an HTTP request
457
     *
458
     * @param string $url
459
     * @param string $method
460
     * @param string $authorization
461
     * @param array $postfields
462
     *
463
     * @return string
464
     * @throws TwitterOAuthException
465
     */
466
    private function request($url, $method, $authorization, array $postfields)
467
    {
468
        $options = $this->curlOptions();
469
        $options[CURLOPT_URL] = $url;
470
        $options[CURLOPT_HTTPHEADER] = ['Accept: application/json', $authorization, 'Expect:'];
471
472
        switch ($method) {
473
            case 'GET':
474
                break;
475
            case 'POST':
476
                $options[CURLOPT_POST] = true;
477
                $options[CURLOPT_POSTFIELDS] = Util::buildHttpQuery($postfields);
478
                break;
479
            case 'DELETE':
480
                $options[CURLOPT_CUSTOMREQUEST] = 'DELETE';
481
                break;
482
            case 'PUT':
483
                $options[CURLOPT_CUSTOMREQUEST] = 'PUT';
484
                break;
485
        }
486
487
        if (in_array($method, ['GET', 'PUT', 'DELETE']) && !empty($postfields)) {
488
            $options[CURLOPT_URL] .= '?' . Util::buildHttpQuery($postfields);
489
        }
490
491
492
        $curlHandle = curl_init();
493
        curl_setopt_array($curlHandle, $options);
494
        $response = curl_exec($curlHandle);
495
496
        // Throw exceptions on cURL errors.
497
        if (curl_errno($curlHandle) > 0) {
498
            throw new TwitterOAuthException(curl_error($curlHandle), curl_errno($curlHandle));
499
        }
500
501
        $this->response->setHttpCode(curl_getinfo($curlHandle, CURLINFO_HTTP_CODE));
502
        $parts = explode("\r\n\r\n", $response);
503
        $responseBody = array_pop($parts);
504
        $responseHeader = array_pop($parts);
505
        $this->response->setHeaders($this->parseHeaders($responseHeader));
506
507
        curl_close($curlHandle);
508
509
        return $responseBody;
510
    }
511
512
    /**
513
     * Get the header info to store.
514
     *
515
     * @param string $header
516
     *
517
     * @return array
518
     */
519
    private function parseHeaders($header)
520
    {
521
        $headers = [];
522
        foreach (explode("\r\n", $header) as $line) {
523
            if (strpos($line, ':') !== false) {
524
                list ($key, $value) = explode(': ', $line);
525
                $key = str_replace('-', '_', strtolower($key));
526
                $headers[$key] = trim($value);
527
            }
528
        }
529
        return $headers;
530
    }
531
532
    /**
533
     * Encode application authorization header with base64.
534
     *
535
     * @param Consumer $consumer
536
     *
537
     * @return string
538
     */
539
    private function encodeAppAuthorization(Consumer $consumer)
540
    {
541
        $key = rawurlencode($consumer->key);
542
        $secret = rawurlencode($consumer->secret);
543
        return base64_encode($key . ':' . $secret);
544
    }
545
546
    /**
547
     * Is the code running from a Phar module.
548
     *
549
     * @return boolean
550
     */
551
    private function pharRunning()
552
    {
553
        return class_exists('Phar') && \Phar::running(false) !== '';
554
    }
555
556
    /**
557
     * Use included CA file instead of OS provided list.
558
     *
559
     * @return boolean
560
     */
561
    private function useCAFile()
562
    {
563
        /* Use CACert file when not in a PHAR file. */
564
        return !$this->pharRunning();
565
    }
566
}
567