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 (#65)
by
unknown
01:25
created

Client::getHeadersForBearerToken()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 6
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 0
1
<?php
2
3
namespace Spatie\Dropbox;
4
5
use Exception;
6
use GrahamCampbell\GuzzleFactory\GuzzleFactory;
7
use GuzzleHttp\Client as GuzzleClient;
8
use GuzzleHttp\Exception\ClientException;
9
use GuzzleHttp\Exception\RequestException;
10
use GuzzleHttp\Psr7;
11
use GuzzleHttp\Psr7\PumpStream;
12
use GuzzleHttp\Psr7\StreamWrapper;
13
use Psr\Http\Message\ResponseInterface;
14
use Psr\Http\Message\StreamInterface;
15
use Spatie\Dropbox\Exceptions\BadRequest;
16
17
class Client
18
{
19
    public const THUMBNAIL_FORMAT_JPEG = 'jpeg';
20
    public const THUMBNAIL_FORMAT_PNG = 'png';
21
22
    public const THUMBNAIL_SIZE_XS = 'w32h32';
23
    public const THUMBNAIL_SIZE_S = 'w64h64';
24
    public const THUMBNAIL_SIZE_M = 'w128h128';
25
    public const THUMBNAIL_SIZE_L = 'w640h480';
26
    public const THUMBNAIL_SIZE_XL = 'w1024h768';
27
28
    public const MAX_CHUNK_SIZE = 1024 * 1024 * 150;
29
30
    public const UPLOAD_SESSION_START = 0;
31
    public const UPLOAD_SESSION_APPEND = 1;
32
33
    /**
34
     * @var string
35
     */
36
    protected $accessToken;
37
38
    /**
39
     * @var string
40
     */
41
    protected $appKey;
42
43
    /**
44
     * @var string
45
     */
46
    protected $appSecret;
47
48
    /**
49
     * @var \GuzzleHttp\Client
50
     */
51
    protected $client;
52
53
    /**
54
     * @var int
55
     */
56
    protected $maxChunkSize;
57
58
    /**
59
     * @var int
60
     */
61
    protected $maxUploadChunkRetries;
62
63
    /**
64
     * @param string|array|null $accessTokenOrAppCredentials
65
     * @param GuzzleClient|null $client
66
     * @param int $maxChunkSize Set max chunk size per request (determines when to switch from "one shot upload" to upload session and defines chunk size for uploads via session).
67
     * @param int $maxUploadChunkRetries How many times to retry an upload session start or append after RequestException.
68
     */
69
    public function __construct($accessTokenOrAppCredentials = null, GuzzleClient $client = null, int $maxChunkSize = self::MAX_CHUNK_SIZE, int $maxUploadChunkRetries = 0)
70
    {
71
        if (is_array($accessTokenOrAppCredentials)) {
72
            [$this->appKey, $this->appSecret] = $accessTokenOrAppCredentials;
73
        }
74
        if (is_string($accessTokenOrAppCredentials)) {
75
            $this->accessToken = $accessTokenOrAppCredentials;
76
        }
77
78
        $this->client = $client ?? new GuzzleClient(['handler' => GuzzleFactory::handler()]);
79
80
        $this->maxChunkSize = ($maxChunkSize < self::MAX_CHUNK_SIZE ? ($maxChunkSize > 1 ? $maxChunkSize : 1) : self::MAX_CHUNK_SIZE);
81
        $this->maxUploadChunkRetries = $maxUploadChunkRetries;
82
    }
83
84
    /**
85
     * Copy a file or folder to a different location in the user's Dropbox.
86
     *
87
     * If the source path is a folder all its contents will be copied.
88
     *
89
     * @link https://www.dropbox.com/developers/documentation/http/documentation#files-copy_v2
90
     *
91
     * @param string $fromPath
92
     * @param string $toPath
93
     * @return array
94
     * @throws Exception
95
     */
96
    public function copy(string $fromPath, string $toPath): array
97
    {
98
        $parameters = [
99
            'from_path' => $this->normalizePath($fromPath),
100
            'to_path' => $this->normalizePath($toPath),
101
        ];
102
103
        return $this->rpcEndpointRequest('files/copy_v2', $parameters);
104
    }
105
106
    /**
107
     * Create a folder at a given path.
108
     *
109
     * @link https://www.dropbox.com/developers/documentation/http/documentation#files-create_folder
110
     *
111
     * @param string $path
112
     * @return array
113
     * @throws Exception
114
     */
115
    public function createFolder(string $path): array
116
    {
117
        $parameters = [
118
            'path' => $this->normalizePath($path),
119
        ];
120
121
        $object = $this->rpcEndpointRequest('files/create_folder', $parameters);
122
123
        $object['.tag'] = 'folder';
124
125
        return $object;
126
    }
127
128
    /**
129
     * Create a shared link with custom settings.
130
     *
131
     * If no settings are given then the default visibility is RequestedVisibility.public.
132
     * The resolved visibility, though, may depend on other aspects such as team and
133
     * shared folder settings). Only for paid users.
134
     *
135
     * @link https://www.dropbox.com/developers/documentation/http/documentation#sharing-create_shared_link_with_settings
136
     *
137
     * @param string $path
138
     * @param array $settings
139
     * @return array
140
     * @throws Exception
141
     */
142
    public function createSharedLinkWithSettings(string $path, array $settings = []): array
143
    {
144
        $parameters = [
145
            'path' => $this->normalizePath($path),
146
        ];
147
148
        if (count($settings)) {
149
            $parameters = array_merge(compact('settings'), $parameters);
150
        }
151
152
        return $this->rpcEndpointRequest('sharing/create_shared_link_with_settings', $parameters);
153
    }
154
155
    /**
156
     * Search a file or folder in the user's Dropbox.
157
     *
158
     * @link https://www.dropbox.com/developers/documentation/http/documentation#files-search
159
     *
160
     * @param string $query
161
     * @param bool $includeHighlights
162
     * @return array
163
     * @throws Exception
164
     */
165
    public function search(string $query, bool $includeHighlights = false): array
166
    {
167
        $parameters = [
168
            'query' => $query,
169
            'include_highlights' => $includeHighlights,
170
        ];
171
172
        return $this->rpcEndpointRequest('files/search_v2', $parameters);
173
    }
174
175
    /**
176
     * List shared links.
177
     *
178
     * For empty path returns a list of all shared links. For non-empty path
179
     * returns a list of all shared links with access to the given path.
180
     *
181
     * If direct_only is set true, only direct links to the path will be returned, otherwise
182
     * it may return link to the path itself and parent folders as described on docs.
183
     *
184
     * @link https://www.dropbox.com/developers/documentation/http/documentation#sharing-list_shared_links
185
     *
186
     * @param string|null $path
187
     * @param bool $direct_only
188
     * @param string|null $cursor
189
     * @return array
190
     * @throws Exception
191
     */
192
    public function listSharedLinks(string $path = null, bool $direct_only = false, string $cursor = null): array
193
    {
194
        $parameters = [
195
            'path' => $path ? $this->normalizePath($path) : null,
196
            'cursor' => $cursor,
197
            'direct_only' => $direct_only,
198
        ];
199
200
        $body = $this->rpcEndpointRequest('sharing/list_shared_links', $parameters);
201
202
        return $body['links'];
203
    }
204
205
    /**
206
     * Delete the file or folder at a given path.
207
     *
208
     * If the path is a folder, all its contents will be deleted too.
209
     * A successful response indicates that the file or folder was deleted.
210
     *
211
     * @link https://www.dropbox.com/developers/documentation/http/documentation#files-delete
212
     *
213
     * @param string $path
214
     * @return array
215
     * @throws Exception
216
     */
217
    public function delete(string $path): array
218
    {
219
        $parameters = [
220
            'path' => $this->normalizePath($path),
221
        ];
222
223
        return $this->rpcEndpointRequest('files/delete', $parameters);
224
    }
225
226
    /**
227
     * Download a file from a user's Dropbox.
228
     *
229
     * @link https://www.dropbox.com/developers/documentation/http/documentation#files-download
230
     *
231
     * @param string $path
232
     * @return resource
233
     * @throws Exception
234
     */
235
    public function download(string $path)
236
    {
237
        $arguments = [
238
            'path' => $this->normalizePath($path),
239
        ];
240
241
        $response = $this->contentEndpointRequest('files/download', $arguments);
242
243
        return StreamWrapper::getResource($response->getBody());
244
    }
245
246
    /**
247
     * Download a folder from the user's Dropbox, as a zip file.
248
     * The folder must be less than 20 GB in size and have fewer than 10,000 total files.
249
     * The input cannot be a single file. Any single file must be less than 4GB in size.
250
     *
251
     * @param string $path
252
     *
253
     * @return resource
254
     *
255
     * @link https://www.dropbox.com/developers/documentation/http/documentation#files-download_zip
256
     */
257
    public function downloadZip(string $path)
258
    {
259
        $arguments = [
260
            'path' => $this->normalizePath($path),
261
        ];
262
263
        $response = $this->contentEndpointRequest('files/download_zip', $arguments);
264
265
        return StreamWrapper::getResource($response->getBody());
266
    }
267
268
    /**
269
     * Returns the metadata for a file or folder.
270
     *
271
     * Note: Metadata for the root folder is unsupported.
272
     *
273
     * @link https://www.dropbox.com/developers/documentation/http/documentation#files-get_metadata
274
     *
275
     * @param string $path
276
     * @return array
277
     * @throws Exception
278
     */
279
    public function getMetadata(string $path): array
280
    {
281
        $parameters = [
282
            'path' => $this->normalizePath($path),
283
        ];
284
285
        return $this->rpcEndpointRequest('files/get_metadata', $parameters);
286
    }
287
288
    /**
289
     * Get a temporary link to stream content of a file.
290
     *
291
     * This link will expire in four hours and afterwards you will get 410 Gone.
292
     * Content-Type of the link is determined automatically by the file's mime type.
293
     *
294
     * @link https://www.dropbox.com/developers/documentation/http/documentation#files-get_temporary_link
295
     *
296
     * @param string $path
297
     * @return string
298
     * @throws Exception
299
     */
300
    public function getTemporaryLink(string $path): string
301
    {
302
        $parameters = [
303
            'path' => $this->normalizePath($path),
304
        ];
305
306
        $body = $this->rpcEndpointRequest('files/get_temporary_link', $parameters);
307
308
        return $body['link'];
309
    }
310
311
    /**
312
     * Get a thumbnail for an image.
313
     *
314
     * This method currently supports files with the following file extensions:
315
     * jpg, jpeg, png, tiff, tif, gif and bmp.
316
     *
317
     * Photos that are larger than 20MB in size won't be converted to a thumbnail.
318
     *
319
     * @link https://www.dropbox.com/developers/documentation/http/documentation#files-get_thumbnail
320
     *
321
     * @param string $path
322
     * @param string $format
323
     * @param string $size
324
     * @return string
325
     * @throws Exception
326
     */
327
    public function getThumbnail(string $path, string $format = 'jpeg', string $size = 'w64h64'): string
328
    {
329
        $arguments = [
330
            'path' => $this->normalizePath($path),
331
            'format' => $format,
332
            'size' => $size,
333
        ];
334
335
        $response = $this->contentEndpointRequest('files/get_thumbnail', $arguments);
336
337
        return (string) $response->getBody();
338
    }
339
340
    /**
341
     * Starts returning the contents of a folder.
342
     *
343
     * If the result's ListFolderResult.has_more field is true, call
344
     * list_folder/continue with the returned ListFolderResult.cursor to retrieve more entries.
345
     *
346
     * Note: auth.RateLimitError may be returned if multiple list_folder or list_folder/continue calls
347
     * with same parameters are made simultaneously by same API app for same user. If your app implements
348
     * retry logic, please hold off the retry until the previous request finishes.
349
     *
350
     * @link https://www.dropbox.com/developers/documentation/http/documentation#files-list_folder
351
     *
352
     * @param string $path
353
     * @param bool $recursive
354
     * @return array
355
     * @throws Exception
356
     */
357
    public function listFolder(string $path = '', bool $recursive = false): array
358
    {
359
        $parameters = [
360
            'path' => $this->normalizePath($path),
361
            'recursive' => $recursive,
362
        ];
363
364
        return $this->rpcEndpointRequest('files/list_folder', $parameters);
365
    }
366
367
    /**
368
     * Once a cursor has been retrieved from list_folder, use this to paginate through all files and
369
     * retrieve updates to the folder, following the same rules as documented for list_folder.
370
     *
371
     * @link https://www.dropbox.com/developers/documentation/http/documentation#files-list_folder-continue
372
     *
373
     * @param string $cursor
374
     * @return array
375
     * @throws Exception
376
     */
377
    public function listFolderContinue(string $cursor = ''): array
378
    {
379
        return $this->rpcEndpointRequest('files/list_folder/continue', compact('cursor'));
380
    }
381
382
    /**
383
     * Move a file or folder to a different location in the user's Dropbox.
384
     *
385
     * If the source path is a folder all its contents will be moved.
386
     *
387
     * @link https://www.dropbox.com/developers/documentation/http/documentation#files-move_v2
388
     *
389
     * @param string $fromPath
390
     * @param string $toPath
391
     * @return array
392
     * @throws Exception
393
     */
394
    public function move(string $fromPath, string $toPath): array
395
    {
396
        $parameters = [
397
            'from_path' => $this->normalizePath($fromPath),
398
            'to_path' => $this->normalizePath($toPath),
399
        ];
400
401
        return $this->rpcEndpointRequest('files/move_v2', $parameters);
402
    }
403
404
    /**
405
     * The file should be uploaded in chunks if it size exceeds the 150 MB threshold
406
     * or if the resource size could not be determined (eg. a popen() stream).
407
     *
408
     * @param string|resource $contents
409
     * @return bool
410
     */
411
    protected function shouldUploadChunked($contents): bool
412
    {
413
        $size = is_string($contents) ? strlen($contents) : fstat($contents)['size'];
414
415
        if ($this->isPipe($contents)) {
416
            return true;
417
        }
418
419
        if ($size === null) {
420
            return true;
421
        }
422
423
        return $size > $this->maxChunkSize;
424
    }
425
426
    /**
427
     * Check if the contents is a pipe stream (not seekable, no size defined).
428
     *
429
     * @param string|resource $contents
430
     * @return bool
431
     */
432
    protected function isPipe($contents): bool
433
    {
434
        return is_resource($contents) ? (fstat($contents)['mode'] & 010000) != 0 : false;
435
    }
436
437
    /**
438
     * Create a new file with the contents provided in the request.
439
     *
440
     * Do not use this to upload a file larger than 150 MB. Instead, create an upload session with upload_session/start.
441
     *
442
     * @link https://www.dropbox.com/developers/documentation/http/documentation#files-upload
443
     *
444
     * @param string $path
445
     * @param string|resource $contents
446
     * @param string $mode
447
     * @return array
448
     * @throws Exception
449
     */
450
    public function upload(string $path, $contents, $mode = 'add'): array
451
    {
452
        if ($this->shouldUploadChunked($contents)) {
453
            return $this->uploadChunked($path, $contents, $mode);
454
        }
455
456
        $arguments = [
457
            'path' => $this->normalizePath($path),
458
            'mode' => $mode,
459
        ];
460
461
        $response = $this->contentEndpointRequest('files/upload', $arguments, $contents);
0 ignored issues
show
Bug introduced by
It seems like $contents defined by parameter $contents on line 450 can also be of type resource; however, Spatie\Dropbox\Client::contentEndpointRequest() does only seem to accept string, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
462
463
        $metadata = json_decode($response->getBody(), true);
464
465
        $metadata['.tag'] = 'file';
466
467
        return $metadata;
468
    }
469
470
    /**
471
     * Upload file split in chunks. This allows uploading large files, since
472
     * Dropbox API v2 limits the content size to 150MB.
473
     *
474
     * The chunk size will affect directly the memory usage, so be careful.
475
     * Large chunks tends to speed up the upload, while smaller optimizes memory usage.
476
     *
477
     * @param string $path
478
     * @param string|resource $contents
479
     * @param string $mode
480
     * @param int|null $chunkSize
481
     * @return array
482
     * @throws Exception
483
     */
484
    public function uploadChunked(string $path, $contents, $mode = 'add', $chunkSize = null): array
485
    {
486
        if ($chunkSize === null || $chunkSize > $this->maxChunkSize) {
487
            $chunkSize = $this->maxChunkSize;
488
        }
489
490
        $stream = $this->getStream($contents);
491
492
        $cursor = $this->uploadChunk(self::UPLOAD_SESSION_START, $stream, $chunkSize, null);
0 ignored issues
show
Compatibility introduced by
$stream of type object<Psr\Http\Message\StreamInterface> is not a sub-type of object<GuzzleHttp\Psr7\Stream>. It seems like you assume a concrete implementation of the interface Psr\Http\Message\StreamInterface to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
493
494
        while (! $stream->eof()) {
495
            $cursor = $this->uploadChunk(self::UPLOAD_SESSION_APPEND, $stream, $chunkSize, $cursor);
496
        }
497
498
        return $this->uploadSessionFinish('', $cursor, $path, $mode);
499
    }
500
501
    /**
502
     * @param int $type
503
     * @param Psr7\Stream $stream
504
     * @param int $chunkSize
505
     * @param UploadSessionCursor|null $cursor
506
     * @return UploadSessionCursor
507
     * @throws Exception
508
     */
509
    protected function uploadChunk($type, &$stream, $chunkSize, $cursor = null): UploadSessionCursor
510
    {
511
        $maximumTries = $stream->isSeekable() ? $this->maxUploadChunkRetries : 0;
512
        $pos = $stream->tell();
513
514
        $tries = 0;
515
516
        tryUpload:
517
        try {
518
            $tries++;
519
520
            $chunkStream = new Psr7\LimitStream($stream, $chunkSize, $stream->tell());
521
522
            if ($type === self::UPLOAD_SESSION_START) {
523
                return $this->uploadSessionStart($chunkStream);
524
            }
525
526
            if ($type === self::UPLOAD_SESSION_APPEND && $cursor !== null) {
527
                return $this->uploadSessionAppend($chunkStream, $cursor);
528
            }
529
530
            throw new Exception('Invalid type');
531
        } catch (RequestException $exception) {
532
            if ($tries < $maximumTries) {
533
                // rewind
534
                $stream->seek($pos, SEEK_SET);
535
                goto tryUpload;
536
            }
537
            throw $exception;
538
        }
539
    }
540
541
    /**
542
     * Upload sessions allow you to upload a single file in one or more requests,
543
     * for example where the size of the file is greater than 150 MB.
544
     * This call starts a new upload session with the given data.
545
     *
546
     * @link https://www.dropbox.com/developers/documentation/http/documentation#files-upload_session-start
547
     *
548
     * @param string|StreamInterface $contents
549
     * @param bool $close
550
     * @return UploadSessionCursor
551
     * @throws Exception
552
     */
553
    public function uploadSessionStart($contents, bool $close = false): UploadSessionCursor
554
    {
555
        $arguments = compact('close');
556
557
        $response = json_decode(
558
            $this->contentEndpointRequest('files/upload_session/start', $arguments, $contents)->getBody(),
0 ignored issues
show
Bug introduced by
It seems like $contents defined by parameter $contents on line 553 can also be of type object<Psr\Http\Message\StreamInterface>; however, Spatie\Dropbox\Client::contentEndpointRequest() does only seem to accept string, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
559
            true
560
        );
561
562
        return new UploadSessionCursor($response['session_id'], ($contents instanceof StreamInterface ? $contents->tell() : strlen($contents)));
563
    }
564
565
    /**
566
     * Append more data to an upload session.
567
     * When the parameter close is set, this call will close the session.
568
     * A single request should not upload more than 150 MB.
569
     *
570
     * @link https://www.dropbox.com/developers/documentation/http/documentation#files-upload_session-append_v2
571
     *
572
     * @param string|StreamInterface $contents
573
     * @param UploadSessionCursor $cursor
574
     * @param bool $close
575
     * @return UploadSessionCursor
576
     * @throws Exception
577
     */
578
    public function uploadSessionAppend($contents, UploadSessionCursor $cursor, bool $close = false): UploadSessionCursor
579
    {
580
        $arguments = compact('cursor', 'close');
581
582
        $pos = $contents instanceof StreamInterface ? $contents->tell() : 0;
583
        $this->contentEndpointRequest('files/upload_session/append_v2', $arguments, $contents);
0 ignored issues
show
Bug introduced by
It seems like $contents defined by parameter $contents on line 578 can also be of type object<Psr\Http\Message\StreamInterface>; however, Spatie\Dropbox\Client::contentEndpointRequest() does only seem to accept string, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
584
585
        $cursor->offset += $contents instanceof StreamInterface ? ($contents->tell() - $pos) : strlen($contents);
586
587
        return $cursor;
588
    }
589
590
    /**
591
     * Finish an upload session and save the uploaded data to the given file path.
592
     * A single request should not upload more than 150 MB.
593
     *
594
     * @link https://www.dropbox.com/developers/documentation/http/documentation#files-upload_session-finish
595
     *
596
     * @param string|StreamInterface $contents
597
     * @param UploadSessionCursor $cursor
598
     * @param string $path
599
     * @param string|array $mode
600
     * @param bool $autorename
601
     * @param bool $mute
602
     * @return array
603
     * @throws Exception
604
     */
605
    public function uploadSessionFinish($contents, UploadSessionCursor $cursor, string $path, $mode = 'add', $autorename = false, $mute = false): array
606
    {
607
        $arguments = compact('cursor');
608
        $arguments['commit'] = compact('path', 'mode', 'autorename', 'mute');
609
610
        $response = $this->contentEndpointRequest(
611
            'files/upload_session/finish',
612
            $arguments,
613
            ($contents == '') ? null : $contents
0 ignored issues
show
Bug introduced by
It seems like $contents == '' ? null : $contents can also be of type object<Psr\Http\Message\StreamInterface>; however, Spatie\Dropbox\Client::contentEndpointRequest() does only seem to accept string, 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...
614
        );
615
616
        $metadata = json_decode($response->getBody(), true);
617
618
        $metadata['.tag'] = 'file';
619
620
        return $metadata;
621
    }
622
623
    /**
624
     * Get Account Info for current authenticated user.
625
     *
626
     * @link https://www.dropbox.com/developers/documentation/http/documentation#users-get_current_account
627
     *
628
     * @return array
629
     * @throws Exception
630
     */
631
    public function getAccountInfo(): array
632
    {
633
        return $this->rpcEndpointRequest('users/get_current_account');
634
    }
635
636
    /**
637
     * Revoke current access token.
638
     *
639
     * @link https://www.dropbox.com/developers/documentation/http/documentation#auth-token-revoke
640
     *
641
     * @throws Exception
642
     */
643
    public function revokeToken(): void
644
    {
645
        $this->rpcEndpointRequest('auth/token/revoke');
646
    }
647
648
    /**
649
     * @param string $path
650
     * @return string
651
     */
652
    protected function normalizePath(string $path): string
653
    {
654
        if (preg_match("/^id:.*|^rev:.*|^(ns:[0-9]+(\/.*)?)/", $path) === 1) {
655
            return $path;
656
        }
657
658
        $path = trim($path, '/');
659
660
        return ($path === '') ? '' : '/'.$path;
661
    }
662
663
    /**
664
     * @param string $subdomain
665
     * @param string $endpoint
666
     * @return string
667
     */
668
    protected function getEndpointUrl(string $subdomain, string $endpoint): string
669
    {
670
        if (count($parts = explode('::', $endpoint)) === 2) {
671
            [$subdomain, $endpoint] = $parts;
672
        }
673
674
        return "https://{$subdomain}.dropboxapi.com/2/{$endpoint}";
675
    }
676
677
    /**
678
     * @param string $endpoint
679
     * @param array $arguments
680
     * @param string $body
681
     * @return ResponseInterface
682
     * @throws Exception
683
     */
684
    public function contentEndpointRequest(string $endpoint, array $arguments, $body = ''): ResponseInterface
685
    {
686
        $headers = ['Dropbox-API-Arg' => json_encode($arguments)];
687
688
        if ($body !== '') {
689
            $headers['Content-Type'] = 'application/octet-stream';
690
        }
691
692
        try {
693
            $response = $this->client->post($this->getEndpointUrl('content', $endpoint), [
694
                'headers' => $this->getHeaders($headers),
695
                'body' => $body,
696
            ]);
697
        } catch (ClientException $exception) {
698
            throw $this->determineException($exception);
699
        }
700
701
        return $response;
702
    }
703
704
    /**
705
     * @param string $endpoint
706
     * @param array|null $parameters
707
     * @return array
708
     * @throws Exception
709
     */
710
    public function rpcEndpointRequest(string $endpoint, array $parameters = null): array
711
    {
712
        try {
713
            $options = ['headers' => $this->getHeaders()];
714
715
            if ($parameters) {
716
                $options['json'] = $parameters;
717
            }
718
719
            $response = $this->client->post($this->getEndpointUrl('api', $endpoint), $options);
720
        } catch (ClientException $exception) {
721
            throw $this->determineException($exception);
722
        }
723
724
        $response = json_decode($response->getBody(), true);
725
726
        return $response ?? [];
727
    }
728
729
    /**
730
     * @param ClientException $exception
731
     * @return Exception
732
     */
733
    protected function determineException(ClientException $exception): Exception
734
    {
735
        if (in_array($exception->getResponse()->getStatusCode(), [400, 409])) {
736
            return new BadRequest($exception->getResponse());
0 ignored issues
show
Bug introduced by
It seems like $exception->getResponse() can be null; however, __construct() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
737
        }
738
739
        return $exception;
740
    }
741
742
    /**
743
     * @param $contents
744
     * @return PumpStream|StreamInterface
745
     */
746
    protected function getStream($contents)
747
    {
748
        if ($this->isPipe($contents)) {
749
            /* @var resource $contents */
750
            return new PumpStream(function ($length) use ($contents) {
751
                $data = fread($contents, $length);
752
                if (strlen($data) === 0) {
753
                    return false;
754
                }
755
756
                return $data;
757
            });
758
        }
759
760
        return Psr7\stream_for($contents);
761
    }
762
763
    /**
764
     * Get the access token.
765
     *
766
     * @return string
767
     */
768
    public function getAccessToken(): string
769
    {
770
        return $this->accessToken;
771
    }
772
773
    /**
774
     * Set the access token.
775
     *
776
     * @param string $accessToken
777
     * @return Client
778
     */
779
    public function setAccessToken(string $accessToken): self
780
    {
781
        $this->accessToken = $accessToken;
782
783
        return $this;
784
    }
785
786
    /**
787
     * Get the HTTP headers.
788
     *
789
     * @param array $headers
790
     * @return array
791
     */
792
    protected function getHeaders(array $headers = []): array
793
    {
794
        $auth = [];
795
        if ($this->accessToken || ($this->appKey && $this->appSecret)) {
796
            $auth = $this->accessToken ? $this->getHeadersForBearerToken() : $this->getHeadersForCredentials();
797
        }
798
799
        return array_merge($auth, $headers);
800
    }
801
802
    /**
803
     * @return array
804
     */
805
    protected function getHeadersForBearerToken()
806
    {
807
        return [
808
            'Authorization' => "Bearer {$this->accessToken}",
809
        ];
810
    }
811
812
    /**
813
     * @return array
814
     */
815
    protected function getHeadersForCredentials()
816
    {
817
        return [
818
            'Authorization' => 'Basic '.base64_encode("{$this->appKey}:{$this->appSecret}"),
819
        ];
820
    }
821
}
822