Completed
Pull Request — master (#66)
by
unknown
02:32 queued 36s
created

Server::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 1

Importance

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