Completed
Pull Request — master (#98)
by Rajendra
03:09
created

Server::verifyPatchRequest()   B

Complexity

Conditions 8
Paths 5

Size

Total Lines 24
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 9.8645

Importance

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