Server::verifyPatchRequest()   A
last analyzed

Complexity

Conditions 5
Paths 4

Size

Total Lines 19
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 5

Importance

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