Completed
Pull Request — master (#184)
by
unknown
04:28
created

Server   F

Complexity

Total Complexity 91

Size/Duplication

Total Lines 820
Duplicated Lines 0 %

Test Coverage

Coverage 98.92%

Importance

Changes 9
Bugs 0 Features 0
Metric Value
eloc 266
c 9
b 0
f 0
dl 0
loc 820
ccs 276
cts 279
cp 0.9892
rs 2
wmc 91

36 Methods

Rating   Name   Duplication   Size   Complexity  
A handlePost() 0 49 5
A getSupportedHashAlgorithms() 0 14 3
A isExpired() 0 9 4
A getPartialsMeta() 0 11 2
A handleConcatenation() 0 43 3
A handleGet() 0 8 2
A getMaxUploadSize() 0 3 1
A handleHead() 0 15 3
A verifyUploadSize() 0 9 3
A serve() 0 21 4
A getHeadersForHeadRequest() 0 17 4
A handleDownload() 0 17 4
A getServerChecksum() 0 3 1
A getClientChecksum() 0 17 4
A __construct() 0 8 1
B handlePatch() 0 49 8
A getUploadDir() 0 3 1
A setUploadKey() 0 5 1
A verifyChecksum() 0 8 2
A setMaxUploadSize() 0 5 1
A verifyPatchRequest() 0 19 5
A buildFile() 0 9 2
A handleExpiration() 0 24 5
A setUploadDir() 0 5 1
A getChecksumAlgorithm() 0 11 2
A setRequest() 0 5 1
A handleOptions() 0 16 2
A handleDelete() 0 20 4
A getUploadKey() 0 15 3
A applyMiddleware() 0 6 2
A getResponse() 0 3 1
A getPathForPartialUpload() 0 11 2
A middleware() 0 3 1
A __call() 0 3 1
A getRequest() 0 3 1
A setMiddleware() 0 5 1

How to fix   Complexity   

Complex Class

Complex classes like Server often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Server, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace TusPhp\Tus;
4
5
use TusPhp\File;
6
use Carbon\Carbon;
7
use TusPhp\Request;
8
use TusPhp\Response;
9
use Ramsey\Uuid\Uuid;
10
use TusPhp\Cache\Cacheable;
11
use TusPhp\Events\UploadMerged;
12
use TusPhp\Events\UploadCreated;
13
use TusPhp\Events\UploadComplete;
14
use TusPhp\Events\UploadProgress;
15
use TusPhp\Middleware\Middleware;
16
use TusPhp\Exception\FileException;
17
use TusPhp\Exception\ConnectionException;
18
use TusPhp\Exception\OutOfRangeException;
19
use Symfony\Component\HttpFoundation\BinaryFileResponse;
20
use Symfony\Component\HttpFoundation\Request as HttpRequest;
21
use Symfony\Component\HttpFoundation\Response as HttpResponse;
22
23
class Server extends AbstractTus
24
{
25
    /** @const string Tus Creation Extension */
26
    const TUS_EXTENSION_CREATION = 'creation';
27
28
    /** @const string Tus Termination Extension */
29
    const TUS_EXTENSION_TERMINATION = 'termination';
30
31
    /** @const string Tus Checksum Extension */
32
    const TUS_EXTENSION_CHECKSUM = 'checksum';
33
34
    /** @const string Tus Expiration Extension */
35
    const TUS_EXTENSION_EXPIRATION = 'expiration';
36
37
    /** @const string Tus Concatenation Extension */
38
    const TUS_EXTENSION_CONCATENATION = 'concatenation';
39
40
    /** @const array All supported tus extensions */
41
    const TUS_EXTENSIONS = [
42
        self::TUS_EXTENSION_CREATION,
43
        self::TUS_EXTENSION_TERMINATION,
44
        self::TUS_EXTENSION_CHECKSUM,
45
        self::TUS_EXTENSION_EXPIRATION,
46
        self::TUS_EXTENSION_CONCATENATION,
47
    ];
48
49
    /** @const int 460 Checksum Mismatch */
50
    const HTTP_CHECKSUM_MISMATCH = 460;
51
52
    /** @const string Default checksum algorithm */
53
    const DEFAULT_CHECKSUM_ALGORITHM = 'sha256';
54
55
    /** @var Request */
56
    protected $request;
57
58
    /** @var Response */
59
    protected $response;
60
61
    /** @var string */
62
    protected $uploadDir;
63
64
    /** @var string */
65
    protected $uploadKey;
66
67
    /** @var Middleware */
68
    protected $middleware;
69
70
    /**
71
     * @var int Max upload size in bytes
72
     *          Default 0, no restriction.
73
     */
74
    protected $maxUploadSize = 0;
75
76
    /**
77
     * TusServer constructor.
78
     *
79
     * @param Cacheable|string $cacheAdapter
80
     *
81
     * @throws \ReflectionException
82
     */
83 3
    public function __construct($cacheAdapter = 'file')
84
    {
85 3
        $this->request    = new Request;
86 3
        $this->response   = new Response;
87 3
        $this->middleware = new Middleware;
88 3
        $this->uploadDir  = dirname(__DIR__, 2) . '/' . 'uploads';
89
90 3
        $this->setCache($cacheAdapter);
91 3
    }
92
93
    /**
94
     * Set the request
95
     *
96
     * @param \Symfony\Component\HttpFoundation\Request $request
97
     * @return \TusPhp\Tus\Server
98
     */
99
    public function setRequest(HttpRequest $request): self
100
    {
101
        $this->request->setRequest($request);
102
103
        return $this;
104
    }
105
106
    /**
107
     * Set upload dir.
108
     *
109
     * @param string $path
110
     *
111
     * @return Server
112
     */
113 2
    public function setUploadDir(string $path) : self
114
    {
115 2
        $this->uploadDir = $path;
116
117 2
        return $this;
118
    }
119
120
    /**
121
     * Get upload dir.
122
     *
123
     * @return string
124
     */
125 1
    public function getUploadDir() : string
126
    {
127 1
        return $this->uploadDir;
128
    }
129
130
    /**
131
     * Get request.
132
     *
133
     * @return Request
134
     */
135 1
    public function getRequest() : Request
136
    {
137 1
        return $this->request;
138
    }
139
140
    /**
141
     * Get request.
142
     *
143
     * @return Response
144
     */
145 1
    public function getResponse() : Response
146
    {
147 1
        return $this->response;
148
    }
149
150
    /**
151
     * Get file checksum.
152
     *
153
     * @param string $filePath
154
     *
155
     * @return string
156
     */
157 1
    public function getServerChecksum(string $filePath) : string
158
    {
159 1
        return hash_file($this->getChecksumAlgorithm(), $filePath);
160
    }
161
162
    /**
163
     * Get checksum algorithm.
164
     *
165
     * @return string|null
166
     */
167 1
    public function getChecksumAlgorithm() : ?string
168
    {
169 1
        $checksumHeader = $this->getRequest()->header('Upload-Checksum');
170
171 1
        if (empty($checksumHeader)) {
172 1
            return self::DEFAULT_CHECKSUM_ALGORITHM;
173
        }
174
175 1
        list($checksumAlgorithm, /* $checksum */) = explode(' ', $checksumHeader);
176
177 1
        return $checksumAlgorithm;
178
    }
179
180
    /**
181
     * Set upload key.
182
     *
183
     * @param string $key
184
     *
185
     * @return Server
186
     */
187 1
    public function setUploadKey(string $key) : self
188
    {
189 1
        $this->uploadKey = $key;
190
191 1
        return $this;
192
    }
193
194
    /**
195
     * Get upload key from header.
196
     *
197
     * @return string|HttpResponse
198
     */
199 4
    public function getUploadKey()
200
    {
201 4
        if ( ! empty($this->uploadKey)) {
202 1
            return $this->uploadKey;
203
        }
204
205 3
        $key = $this->getRequest()->header('Upload-Key') ?? Uuid::uuid4()->toString();
206
207 3
        if (empty($key)) {
208 1
            return $this->response->send(null, HttpResponse::HTTP_BAD_REQUEST);
209
        }
210
211 2
        $this->uploadKey = $key;
212
213 2
        return $this->uploadKey;
214
    }
215
216
    /**
217
     * Set middleware.
218
     *
219
     * @param Middleware $middleware
220
     *
221
     * @return self
222
     */
223 1
    public function setMiddleware(Middleware $middleware) : self
224
    {
225 1
        $this->middleware = $middleware;
226
227 1
        return $this;
228
    }
229
230
    /**
231
     * Get middleware.
232
     *
233
     * @return Middleware
234
     */
235 1
    public function middleware() : Middleware
236
    {
237 1
        return $this->middleware;
238
    }
239
240
    /**
241
     * Set max upload size.
242
     *
243
     * @param int $uploadSize
244
     *
245
     * @return Server
246
     */
247 2
    public function setMaxUploadSize(int $uploadSize) : self
248
    {
249 2
        $this->maxUploadSize = $uploadSize;
250
251 2
        return $this;
252
    }
253
254
    /**
255
     * Get max upload size.
256
     *
257
     * @return int
258
     */
259 1
    public function getMaxUploadSize() : int
260
    {
261 1
        return $this->maxUploadSize;
262
    }
263
264
    /**
265
     * Handle all HTTP request.
266
     *
267
     * @return HttpResponse|BinaryFileResponse
268
     */
269 5
    public function serve()
270
    {
271 5
        $this->applyMiddleware();
272
273 5
        $requestMethod = $this->getRequest()->method();
274
275 5
        if ( ! in_array($requestMethod, $this->getRequest()->allowedHttpVerbs())) {
276 1
            return $this->response->send(null, HttpResponse::HTTP_METHOD_NOT_ALLOWED);
277
        }
278
279 4
        $clientVersion = $this->getRequest()->header('Tus-Resumable');
280
281 4
        if ($clientVersion && $clientVersion !== self::TUS_PROTOCOL_VERSION) {
282 1
            return $this->response->send(null, HttpResponse::HTTP_PRECONDITION_FAILED, [
283 1
                'Tus-Version' => self::TUS_PROTOCOL_VERSION,
284
            ]);
285
        }
286
287 3
        $method = 'handle' . ucfirst(strtolower($requestMethod));
288
289 3
        return $this->{$method}();
290
    }
291
292
    /**
293
     * Apply middleware.
294
     *
295
     * @return void
296
     */
297 1
    protected function applyMiddleware()
298
    {
299 1
        $middleware = $this->middleware()->list();
300
301 1
        foreach ($middleware as $m) {
302 1
            $m->handle($this->getRequest(), $this->getResponse());
303
        }
304 1
    }
305
306
    /**
307
     * Handle OPTIONS request.
308
     *
309
     * @return HttpResponse
310
     */
311 2
    protected function handleOptions() : HttpResponse
312
    {
313
        $headers = [
314 2
            'Allow' => implode(',', $this->request->allowedHttpVerbs()),
315 2
            'Tus-Version' => self::TUS_PROTOCOL_VERSION,
316 2
            'Tus-Extension' => implode(',', self::TUS_EXTENSIONS),
317 2
            'Tus-Checksum-Algorithm' => $this->getSupportedHashAlgorithms(),
318
        ];
319
320 2
        $maxUploadSize = $this->getMaxUploadSize();
321
322 2
        if ($maxUploadSize > 0) {
323 1
            $headers['Tus-Max-Size'] = $maxUploadSize;
324
        }
325
326 2
        return $this->response->send(null, HttpResponse::HTTP_OK, $headers);
327
    }
328
329
    /**
330
     * Handle HEAD request.
331
     *
332
     * @return HttpResponse
333
     */
334 5
    protected function handleHead() : HttpResponse
335
    {
336 5
        $key = $this->request->key();
337
338 5
        if ( ! $fileMeta = $this->cache->get($key)) {
339 1
            return $this->response->send(null, HttpResponse::HTTP_NOT_FOUND);
340
        }
341
342 4
        $offset = $fileMeta['offset'] ?? false;
343
344 4
        if (false === $offset) {
345 1
            return $this->response->send(null, HttpResponse::HTTP_GONE);
346
        }
347
348 3
        return $this->response->send(null, HttpResponse::HTTP_OK, $this->getHeadersForHeadRequest($fileMeta));
349
    }
350
351
    /**
352
     * Handle POST request.
353
     *
354
     * @return HttpResponse
355
     */
356 5
    protected function handlePost() : HttpResponse
357
    {
358 5
        $fileName   = $this->getRequest()->extractFileName();
359 5
        $uploadType = self::UPLOAD_TYPE_NORMAL;
360
361 5
        if (empty($fileName)) {
362 1
            return $this->response->send(null, HttpResponse::HTTP_BAD_REQUEST);
363
        }
364
365 4
        if ( ! $this->verifyUploadSize()) {
366 1
            return $this->response->send(null, HttpResponse::HTTP_REQUEST_ENTITY_TOO_LARGE);
367
        }
368
369 3
        $uploadKey = $this->getUploadKey();
370 3
        $filePath  = $this->uploadDir . '/' . $fileName;
371
372 3
        if ($this->getRequest()->isFinal()) {
373 1
            return $this->handleConcatenation($fileName, $filePath);
374
        }
375
376 2
        if ($this->getRequest()->isPartial()) {
377 1
            $filePath   = $this->getPathForPartialUpload($uploadKey) . $fileName;
378 1
            $uploadType = self::UPLOAD_TYPE_PARTIAL;
379
        }
380
381 2
        $checksum = $this->getClientChecksum();
382 2
        $location = $this->getRequest()->url() . $this->getApiPath() . '/' . $uploadKey;
383
384 2
        $file = $this->buildFile([
385 2
            'name' => $fileName,
386 2
            'offset' => 0,
387 2
            'size' => $this->getRequest()->header('Upload-Length'),
388 2
            'file_path' => $filePath,
389 2
            'location' => $location,
390 2
        ])->setKey($uploadKey)->setChecksum($checksum)->setUploadMetadata($this->getRequest()->extractAllMeta());
391
392 2
        $this->cache->set($uploadKey, $file->details() + ['upload_type' => $uploadType]);
393
394 2
        $this->event()->dispatch(
395 2
            UploadCreated::NAME,
396 2
            new UploadCreated($file, $this->getRequest(), $this->getResponse())
397
        );
398
399 2
        return $this->response->send(
400 2
            null,
401 2
            HttpResponse::HTTP_CREATED,
402
            [
403 2
                'Location' => $location,
404 2
                'Upload-Expires' => $this->cache->get($uploadKey)['expires_at'],
405
            ]
406
        );
407
    }
408
409
    /**
410
     * Handle file concatenation.
411
     *
412
     * @param string $fileName
413
     * @param string $filePath
414
     *
415
     * @return HttpResponse
416
     */
417 2
    protected function handleConcatenation(string $fileName, string $filePath) : HttpResponse
418
    {
419 2
        $partials  = $this->getRequest()->extractPartials();
420 2
        $uploadKey = $this->getUploadKey();
421 2
        $files     = $this->getPartialsMeta($partials);
422 2
        $filePaths = array_column($files, 'file_path');
423 2
        $location  = $this->getRequest()->url() . $this->getApiPath() . '/' . $uploadKey;
424
425 2
        $file = $this->buildFile([
426 2
            'name' => $fileName,
427 2
            'offset' => 0,
428 2
            'size' => 0,
429 2
            'file_path' => $filePath,
430 2
            'location' => $location,
431 2
        ])->setFilePath($filePath)->setKey($uploadKey)->setUploadMetadata($this->getRequest()->extractAllMeta());
432
433 2
        $file->setOffset($file->merge($files));
434
435
        // Verify checksum.
436 2
        $checksum = $this->getServerChecksum($filePath);
437
438 2
        if ($checksum !== $this->getClientChecksum()) {
439 1
            return $this->response->send(null, self::HTTP_CHECKSUM_MISMATCH);
440
        }
441
442 1
        $file->setChecksum($checksum);
443 1
        $this->cache->set($uploadKey, $file->details() + ['upload_type' => self::UPLOAD_TYPE_FINAL]);
444
445
        // Cleanup.
446 1
        if ($file->delete($filePaths, true)) {
447 1
            $this->cache->deleteAll($partials);
448
        }
449
450 1
        $this->event()->dispatch(
451 1
            UploadMerged::NAME,
452 1
            new UploadMerged($file, $this->getRequest(), $this->getResponse())
453
        );
454
455 1
        return $this->response->send(
456 1
            ['data' => ['checksum' => $checksum]],
457 1
            HttpResponse::HTTP_CREATED,
458
            [
459 1
                'Location' => $location,
460
            ]
461
        );
462
    }
463
464
    /**
465
     * Handle PATCH request.
466
     *
467
     * @return HttpResponse
468
     */
469 10
    protected function handlePatch() : HttpResponse
470
    {
471 10
        $uploadKey = $this->request->key();
472
473 10
        if ( ! $meta = $this->cache->get($uploadKey)) {
474 1
            return $this->response->send(null, HttpResponse::HTTP_GONE);
475
        }
476
477 9
        $status = $this->verifyPatchRequest($meta);
478
479 9
        if (HttpResponse::HTTP_OK !== $status) {
480 3
            return $this->response->send(null, $status);
481
        }
482
483 6
        $file     = $this->buildFile($meta)->setUploadMetadata($meta['metadata'] ?? []);
484 6
        $checksum = $meta['checksum'];
485
486
        try {
487 6
            $fileSize = $file->getFileSize();
488 6
            $offset   = $file->setKey($uploadKey)->setChecksum($checksum)->upload($fileSize);
489
490
            // If upload is done, verify checksum.
491 3
            if ($offset === $fileSize) {
492 2
                if ( ! $this->verifyChecksum($checksum, $meta['file_path'])) {
493 1
                    return $this->response->send(null, self::HTTP_CHECKSUM_MISMATCH);
494
                }
495
496 1
                $this->event()->dispatch(
497 1
                    UploadComplete::NAME,
498 1
                    new UploadComplete($file, $this->getRequest(), $this->getResponse())
499
                );
500
            } else {
501 1
                $this->event()->dispatch(
502 1
                    UploadProgress::NAME,
503 2
                    new UploadProgress($file, $this->getRequest(), $this->getResponse())
504
                );
505
            }
506 3
        } catch (FileException $e) {
507 1
            return $this->response->send($e->getMessage(), HttpResponse::HTTP_UNPROCESSABLE_ENTITY);
508 2
        } catch (OutOfRangeException $e) {
509 1
            return $this->response->send(null, HttpResponse::HTTP_REQUESTED_RANGE_NOT_SATISFIABLE);
510 1
        } catch (ConnectionException $e) {
511 1
            return $this->response->send(null, HttpResponse::HTTP_CONTINUE);
512
        }
513
514 2
        return $this->response->send(null, HttpResponse::HTTP_NO_CONTENT, [
515 2
            'Content-Type' => self::HEADER_CONTENT_TYPE,
516 2
            'Upload-Expires' => $this->cache->get($uploadKey)['expires_at'],
517 2
            'Upload-Offset' => $offset,
518
        ]);
519
    }
520
521
    /**
522
     * Verify PATCH request.
523
     *
524
     * @param array $meta
525
     *
526
     * @return int
527
     */
528 9
    protected function verifyPatchRequest(array $meta) : int
529
    {
530 9
        if (self::UPLOAD_TYPE_FINAL === $meta['upload_type']) {
531 1
            return HttpResponse::HTTP_FORBIDDEN;
532
        }
533
534 8
        $uploadOffset = $this->request->header('upload-offset');
535
536 8
        if ($uploadOffset && $uploadOffset !== (string) $meta['offset']) {
537 1
            return HttpResponse::HTTP_CONFLICT;
538
        }
539
540 7
        $contentType = $this->request->header('Content-Type');
541
542 7
        if ($contentType !== self::HEADER_CONTENT_TYPE) {
543 1
            return HTTPRESPONSE::HTTP_UNSUPPORTED_MEDIA_TYPE;
544
        }
545
546 6
        return HttpResponse::HTTP_OK;
547
    }
548
549
    /**
550
     * Handle GET request.
551
     *
552
     * As per RFC7231, we need to treat HEAD and GET as an identical request.
553
     * All major PHP frameworks follows the same and silently transforms each
554
     * HEAD requests to GET.
555
     *
556
     * @return BinaryFileResponse|HttpResponse
557
     */
558 6
    protected function handleGet()
559
    {
560
        // We will treat '/files/<key>/get' as a download request.
561 6
        if ('get' === $this->request->key()) {
562 5
            return $this->handleDownload();
563
        }
564
565 1
        return $this->handleHead();
566
    }
567
568
    /**
569
     * Handle Download request.
570
     *
571
     * @return BinaryFileResponse|HttpResponse
572
     */
573 4
    protected function handleDownload()
574
    {
575 4
        $path = explode('/', str_replace('/get', '', $this->request->path()));
576 4
        $key  = end($path);
577
578 4
        if ( ! $fileMeta = $this->cache->get($key)) {
579 2
            return $this->response->send('404 upload not found.', HttpResponse::HTTP_NOT_FOUND);
580
        }
581
582 2
        $resource = $fileMeta['file_path'] ?? null;
583 2
        $fileName = $fileMeta['name'] ?? null;
584
585 2
        if ( ! $resource || ! file_exists($resource)) {
586 1
            return $this->response->send('404 upload not found.', HttpResponse::HTTP_NOT_FOUND);
587
        }
588
589 1
        return $this->response->download($resource, $fileName);
590
    }
591
592
    /**
593
     * Handle DELETE request.
594
     *
595
     * @return HttpResponse
596
     */
597 3
    protected function handleDelete() : HttpResponse
598
    {
599 3
        $key      = $this->request->key();
600 3
        $fileMeta = $this->cache->get($key);
601 3
        $resource = $fileMeta['file_path'] ?? null;
602
603 3
        if ( ! $resource) {
604 1
            return $this->response->send(null, HttpResponse::HTTP_NOT_FOUND);
605
        }
606
607 2
        $isDeleted = $this->cache->delete($key);
608
609 2
        if ( ! $isDeleted || ! file_exists($resource)) {
610 1
            return $this->response->send(null, HttpResponse::HTTP_GONE);
611
        }
612
613 1
        unlink($resource);
614
615 1
        return $this->response->send(null, HttpResponse::HTTP_NO_CONTENT, [
616 1
            'Tus-Extension' => self::TUS_EXTENSION_TERMINATION,
617
        ]);
618
    }
619
620
    /**
621
     * Get required headers for head request.
622
     *
623
     * @param array $fileMeta
624
     *
625
     * @return array
626
     */
627 4
    protected function getHeadersForHeadRequest(array $fileMeta) : array
628
    {
629
        $headers = [
630 4
            'Upload-Length' => (int) $fileMeta['size'],
631 4
            'Upload-Offset' => (int) $fileMeta['offset'],
632 4
            'Cache-Control' => 'no-store',
633
        ];
634
635 4
        if (self::UPLOAD_TYPE_FINAL === $fileMeta['upload_type'] && $fileMeta['size'] !== $fileMeta['offset']) {
636 2
            unset($headers['Upload-Offset']);
637
        }
638
639 4
        if (self::UPLOAD_TYPE_NORMAL !== $fileMeta['upload_type']) {
640 3
            $headers += ['Upload-Concat' => $fileMeta['upload_type']];
641
        }
642
643 4
        return $headers;
644
    }
645
646
    /**
647
     * Build file object.
648
     *
649
     * @param array $meta
650
     *
651
     * @return File
652
     */
653 1
    protected function buildFile(array $meta) : File
654
    {
655 1
        $file = new File($meta['name'], $this->cache);
656
657 1
        if (array_key_exists('offset', $meta)) {
658 1
            $file->setMeta($meta['offset'], $meta['size'], $meta['file_path'], $meta['location']);
659
        }
660
661 1
        return $file;
662
    }
663
664
    /**
665
     * Get list of supported hash algorithms.
666
     *
667
     * @return string
668
     */
669 1
    protected function getSupportedHashAlgorithms() : string
670
    {
671 1
        $supportedAlgorithms = hash_algos();
672
673 1
        $algorithms = [];
674 1
        foreach ($supportedAlgorithms as $hashAlgo) {
675 1
            if (false !== strpos($hashAlgo, ',')) {
676 1
                $algorithms[] = "'{$hashAlgo}'";
677
            } else {
678 1
                $algorithms[] = $hashAlgo;
679
            }
680
        }
681
682 1
        return implode(',', $algorithms);
683
    }
684
685
    /**
686
     * Verify and get upload checksum from header.
687
     *
688
     * @return string|HttpResponse
689
     */
690 4
    protected function getClientChecksum()
691
    {
692 4
        $checksumHeader = $this->getRequest()->header('Upload-Checksum');
693
694 4
        if (empty($checksumHeader)) {
695 1
            return '';
696
        }
697
698 3
        list($checksumAlgorithm, $checksum) = explode(' ', $checksumHeader);
699
700 3
        $checksum = base64_decode($checksum);
701
702 3
        if ( ! in_array($checksumAlgorithm, hash_algos()) || false === $checksum) {
703 2
            return $this->response->send(null, HttpResponse::HTTP_BAD_REQUEST);
704
        }
705
706 1
        return $checksum;
707
    }
708
709
    /**
710
     * Get expired but incomplete uploads.
711
     *
712
     * @param array|null $contents
713
     *
714
     * @return bool
715
     */
716 3
    protected function isExpired($contents) : bool
717
    {
718 3
        $isExpired = empty($contents['expires_at']) || Carbon::parse($contents['expires_at'])->lt(Carbon::now());
719
720 3
        if ($isExpired && $contents['offset'] !== $contents['size']) {
721 3
            return true;
722
        }
723
724 2
        return false;
725
    }
726
727
    /**
728
     * Get path for partial upload.
729
     *
730
     * @param string $key
731
     *
732
     * @return string
733
     */
734 1
    protected function getPathForPartialUpload(string $key) : string
735
    {
736 1
        list($actualKey, /* $partialUploadKey */) = explode(self::PARTIAL_UPLOAD_NAME_SEPARATOR, $key);
737
738 1
        $path = $this->uploadDir . '/' . $actualKey . '/';
739
740 1
        if ( ! file_exists($path)) {
741 1
            mkdir($path);
742
        }
743
744 1
        return $path;
745
    }
746
747
    /**
748
     * Get metadata of partials.
749
     *
750
     * @param array $partials
751
     *
752
     * @return array
753
     */
754 3
    protected function getPartialsMeta(array $partials) : array
755
    {
756 3
        $files = [];
757
758 3
        foreach ($partials as $partial) {
759 3
            $fileMeta = $this->getCache()->get($partial);
760
761 3
            $files[] = $fileMeta;
762
        }
763
764 3
        return $files;
765
    }
766
767
    /**
768
     * Delete expired resources.
769
     *
770
     * @return array
771
     */
772 2
    public function handleExpiration() : array
773
    {
774 2
        $deleted   = [];
775 2
        $cacheKeys = $this->cache->keys();
776
777 2
        foreach ($cacheKeys as $key) {
778 2
            $fileMeta = $this->cache->get($key, true);
779
780 2
            if ( ! $this->isExpired($fileMeta)) {
781 1
                continue;
782
            }
783
784 2
            if ( ! $this->cache->delete($key)) {
785 1
                continue;
786
            }
787
788 1
            if (is_writable($fileMeta['file_path'])) {
789 1
                unlink($fileMeta['file_path']);
790
            }
791
792 1
            $deleted[] = $fileMeta;
793
        }
794
795 2
        return $deleted;
796
    }
797
798
    /**
799
     * Verify max upload size.
800
     *
801
     * @return bool
802
     */
803 1
    protected function verifyUploadSize() : bool
804
    {
805 1
        $maxUploadSize = $this->getMaxUploadSize();
806
807 1
        if ($maxUploadSize > 0 && $this->getRequest()->header('Upload-Length') > $maxUploadSize) {
808 1
            return false;
809
        }
810
811 1
        return true;
812
    }
813
814
    /**
815
     * Verify checksum if available.
816
     *
817
     * @param string $checksum
818
     * @param string $filePath
819
     *
820
     * @return bool
821
     */
822 1
    protected function verifyChecksum(string $checksum, string $filePath) : bool
823
    {
824
        // Skip if checksum is empty.
825 1
        if (empty($checksum)) {
826 1
            return true;
827
        }
828
829 1
        return $checksum === $this->getServerChecksum($filePath);
830
    }
831
832
    /**
833
     * No other methods are allowed.
834
     *
835
     * @param string $method
836
     * @param array  $params
837
     *
838
     * @return HttpResponse
839
     */
840 1
    public function __call(string $method, array $params)
841
    {
842 1
        return $this->response->send(null, HttpResponse::HTTP_BAD_REQUEST);
843
    }
844
}
845