Passed
Pull Request — 8.0 (#2912)
by
unknown
02:26
created

Request::rootDomain()   C

Complexity

Conditions 14
Paths 65

Size

Total Lines 50
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 25.743

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 14
eloc 22
c 2
b 0
f 0
nc 65
nop 1
dl 0
loc 50
ccs 14
cts 23
cp 0.6087
crap 25.743
rs 6.2666

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
// +----------------------------------------------------------------------
3
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
4
// +----------------------------------------------------------------------
5
// | Copyright (c) 2006~2023 http://thinkphp.cn All rights reserved.
6
// +----------------------------------------------------------------------
7
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
8
// +----------------------------------------------------------------------
9
// | Author: liu21st <[email protected]>
10
// +----------------------------------------------------------------------
11
declare(strict_types=1);
12
13
namespace think;
14
15
use ArrayAccess;
16
use think\facade\Lang;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, think\Lang. Consider defining an alias.

Let?s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let?s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
17
use think\file\UploadedFile;
18
use think\route\Rule;
19
20
/**
21
 * 请求管理类
22
 * @package think
23
 */
24
class Request implements ArrayAccess
25
{
26
    /**
27
     * 兼容PATH_INFO获取
28
     * @var array
29
     */
30
    protected $pathinfoFetch = ['ORIG_PATH_INFO', 'REDIRECT_PATH_INFO', 'REDIRECT_URL'];
31
32
    /**
33
     * PATHINFO变量名 用于兼容模式
34
     * @var string
35
     */
36
    protected $varPathinfo = 's';
37
38
    /**
39
     * 请求类型
40
     * @var string
41
     */
42
    protected $varMethod = '_method';
43
44
    /**
45
     * 表单ajax伪装变量
46
     * @var string
47
     */
48
    protected $varAjax = '_ajax';
49
50
    /**
51
     * 表单pjax伪装变量
52
     * @var string
53
     */
54
    protected $varPjax = '_pjax';
55
56
    /**
57
     * 域名根
58
     * @var string
59
     */
60
    protected $rootDomain = '';
61
62
    /**
63
     * HTTPS代理标识
64
     * @var string
65
     */
66
    protected $httpsAgentName = '';
67
68
    /**
69
     * 前端代理服务器IP
70
     * @var array
71
     */
72
    protected $proxyServerIp = [];
73
74
    /**
75
     * 前端代理服务器真实IP头
76
     * @var array
77
     */
78
    protected $proxyServerIpHeader = ['HTTP_X_REAL_IP', 'HTTP_X_FORWARDED_FOR', 'HTTP_CLIENT_IP', 'HTTP_X_CLIENT_IP', 'HTTP_X_CLUSTER_CLIENT_IP'];
79
80
    /**
81
     * 请求类型
82
     * @var string
83
     */
84
    protected $method;
85
86
    /**
87
     * 域名(含协议及端口)
88
     * @var string
89
     */
90
    protected $domain;
91
92
    /**
93
     * HOST(含端口)
94
     * @var string
95
     */
96
    protected $host;
97
98
    /**
99
     * 子域名
100
     * @var string
101
     */
102
    protected $subDomain;
103
104
    /**
105
     * 泛域名
106
     * @var string
107
     */
108
    protected $panDomain;
109
110
    /**
111
     * 当前URL地址
112
     * @var string
113
     */
114
    protected $url;
115
116
    /**
117
     * 基础URL
118
     * @var string
119
     */
120
    protected $baseUrl;
121
122
    /**
123
     * 当前执行的文件
124
     * @var string
125
     */
126
    protected $baseFile;
127
128
    /**
129
     * 访问的ROOT地址
130
     * @var string
131
     */
132
    protected $root;
133
134
    /**
135
     * pathinfo
136
     * @var string
137
     */
138
    protected $pathinfo;
139
140
    /**
141
     * pathinfo(不含后缀)
142
     * @var string
143
     */
144
    protected $path;
145
146
    /**
147
     * 当前请求的IP地址
148
     * @var string
149
     */
150
    protected $realIP;
151
152
    /**
153
     * 当前控制器名
154
     * @var string
155
     */
156
    protected $controller;
157
158
    /**
159
     * 当前操作名
160
     * @var string
161
     */
162
    protected $action;
163
164
    /**
165
     * 当前请求参数
166
     * @var array
167
     */
168
    protected $param = [];
169
170
    /**
171
     * 当前GET参数
172
     * @var array
173
     */
174
    protected $get = [];
175
176
    /**
177
     * 当前POST参数
178
     * @var array
179
     */
180
    protected $post = [];
181
182
    /**
183
     * 当前REQUEST参数
184
     * @var array
185
     */
186
    protected $request = [];
187
188
    /**
189
     * 当前路由对象
190
     * @var Rule
191
     */
192
    protected $rule;
193
194
    /**
195
     * 当前ROUTE参数
196
     * @var array
197
     */
198
    protected $route = [];
199
200
    /**
201
     * 中间件传递的参数
202
     * @var array
203
     */
204
    protected $middleware = [];
205
206
    /**
207
     * 当前PUT参数
208
     * @var array
209
     */
210
    protected $put;
211
212
    /**
213
     * SESSION对象
214
     * @var Session
215
     */
216
    protected $session;
217
218
    /**
219
     * COOKIE数据
220
     * @var array
221
     */
222
    protected $cookie = [];
223
224
    /**
225
     * ENV对象
226
     * @var Env
227
     */
228
    protected $env;
229
230
    /**
231
     * 当前SERVER参数
232
     * @var array
233
     */
234
    protected $server = [];
235
236
    /**
237
     * 当前FILE参数
238
     * @var array
239
     */
240
    protected $file = [];
241
242
    /**
243
     * 当前HEADER参数
244
     * @var array
245
     */
246
    protected $header = [];
247
248
    /**
249
     * 资源类型定义
250
     * @var array
251
     */
252
    protected $mimeType = [
253
        'xml'   => 'application/xml,text/xml,application/x-xml',
254
        'json'  => 'application/json,text/x-json,application/jsonrequest,text/json',
255
        'js'    => 'text/javascript,application/javascript,application/x-javascript',
256
        'css'   => 'text/css',
257
        'rss'   => 'application/rss+xml',
258
        'yaml'  => 'application/x-yaml,text/yaml',
259
        'atom'  => 'application/atom+xml',
260
        'pdf'   => 'application/pdf',
261
        'text'  => 'text/plain',
262
        'image' => 'image/png,image/jpg,image/jpeg,image/pjpeg,image/gif,image/webp,image/*',
263
        'csv'   => 'text/csv',
264
        'html'  => 'text/html,application/xhtml+xml,*/*',
265
    ];
266
267
    /**
268
     * 当前请求内容
269
     * @var string
270
     */
271
    protected $content;
272
273
    /**
274
     * 全局过滤规则
275
     * @var array
276
     */
277
    protected $filter;
278
279
    /**
280
     * php://input内容
281
     * @var string
282
     */
283
    // php://input
284
    protected $input;
285
286
    /**
287
     * 请求安全Key
288
     * @var string
289
     */
290
    protected $secureKey;
291
292
    /**
293
     * 是否合并Param
294
     * @var bool
295
     */
296
    protected $mergeParam = false;
297
298
    /**
299
     * 架构函数
300
     * @access public
301
     */
302 27
    public function __construct()
303
    {
304
        // 保存 php://input
305 27
        $this->input = file_get_contents('php://input');
306
    }
307
308 27
    public static function __make(App $app)
309
    {
310 27
        $request = new static();
311
312 27
        if (function_exists('apache_request_headers') && $result = apache_request_headers()) {
313
            $header = $result;
314
        } else {
315 27
            $header = [];
316 27
            $server = $_SERVER;
317 27
            foreach ($server as $key => $val) {
318 27
                if (str_starts_with($key, 'HTTP_')) {
319
                    $key          = str_replace('_', '-', strtolower(substr($key, 5)));
320
                    $header[$key] = $val;
321
                }
322
            }
323 27
            if (isset($server['CONTENT_TYPE'])) {
324
                $header['content-type'] = $server['CONTENT_TYPE'];
325
            }
326 27
            if (isset($server['CONTENT_LENGTH'])) {
327
                $header['content-length'] = $server['CONTENT_LENGTH'];
328
            }
329
        }
330
331 27
        $request->header = array_change_key_case($header);
0 ignored issues
show
Bug introduced by
It seems like $header can also be of type true; however, parameter $array of array_change_key_case() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

331
        $request->header = array_change_key_case(/** @scrutinizer ignore-type */ $header);
Loading history...
332 27
        $request->server = $_SERVER;
333 27
        $request->env    = $app->env;
334
335 27
        $inputData = $request->getInputData($request->input);
336
337 27
        $request->get     = $_GET;
338 27
        $request->post    = $_POST ?: $inputData;
339 27
        $request->put     = $inputData;
340 27
        $request->request = $_REQUEST;
341 27
        $request->cookie  = $_COOKIE;
342 27
        $request->file    = $_FILES ?? [];
343
344 27
        return $request;
345
    }
346
347
    /**
348
     * 设置当前包含协议的域名
349
     * @access public
350
     * @param  string $domain 域名
351
     * @return $this
352
     */
353
    public function setDomain(string $domain)
354
    {
355
        $this->domain = $domain;
356
        return $this;
357
    }
358
359
    /**
360
     * 获取当前包含协议的域名
361
     * @access public
362
     * @param  bool $port 是否需要去除端口号
363
     * @return string
364
     */
365
    public function domain(bool $port = false): string
366
    {
367
        return $this->scheme() . '://' . $this->host($port);
368
    }
369
370
    /**
371
     * 获取当前根域名
372
     * @access public
373
     * @param bool $strict true 仅仅获取HOST(不包括端口号)
374
     * @return string
375
     */
376 27
    public function rootDomain(bool $strict = false): string
377
    {
378 27
        $root = $this->rootDomain;
379
380 27
        if (!$root) {
381
            // 获取可能包含端口号的 HOST
382 27
            $host = $this->host();
383 27
            $port = '';
384
385
            // 如果包含端口号,那就提取出来
386 27
            if (str_contains($host, ':')) {
387
                [$host, $port] = explode(':', $host);
388
            }
389
390 27
            $item = explode('.', $host);
391 27
            $count = count($item);
392 27
            $root = $count > 1 ? $item[$count - 2] . '.' . $item[$count - 1] : $item[0];
393
394
            // 获取配置文件里面的公共域名后缀
395 27
            $public_suffix = app('config')->get('app.domain_public_suffix', []);
396
397
            // 如果是字符串,那就转换成数组
398 27
            if(!is_array($public_suffix) && $public_suffix) {
399
                $public_suffix = explode(',', $public_suffix);
400
            }
401
402
            // 按照前面的匹配规则,如果 HOST 是 `thinkphp.com.cn` 那 $root 是 `com.cn`。
403
            // 只会存在一个 `.` ,不满足就跳过,同时 HOST 的长度要大于 2 才能满足分配。
404 27
            if (substr_count($root, '.') === 1 && $count > 2 && count($public_suffix)) {
405
                foreach ($public_suffix as $suffix) {
406
                    // 不止一个 `.` 的有两种情况,第一种是 `cn` `com` 这种,上面就匹配了,这里就不用管了。
407
                    // 第二种太少见就不考虑了。
408
                    if (substr_count($suffix, '.') !== 1) {
409
                        continue;
410
                    }
411
412
                    if (strtolower($suffix) == strtolower($root)) {
413
                        $root = $item[$count - 3] . '.' . $root;
414
                        break;
415
                    }
416
                }
417
            }
418
419
            // 如果不是严格模式且端口号存在,那就把端口号拼接上去,主要兼容 host() 方法的严格模式。
420 27
            if(!$strict && $port){
421
                $root .= ':' . $port;
422
            }
423
        }
424
425 27
        return $root;
426
    }
427
428
    /**
429
     * 设置当前泛域名的值
430
     * @access public
431
     * @param  string $domain 域名
432
     * @return $this
433
     */
434
    public function setSubDomain(string $domain)
435
    {
436
        $this->subDomain = $domain;
437
        return $this;
438
    }
439
440
    /**
441
     * 获取当前子域名
442
     * @access public
443
     * @return string
444
     */
445 27
    public function subDomain(): string
446
    {
447 27
        if (is_null($this->subDomain)) {
0 ignored issues
show
introduced by
The condition is_null($this->subDomain) is always false.
Loading history...
448
            // 获取当前主域名
449 27
            $rootDomain = $this->rootDomain();
450
451 27
            if ($rootDomain) {
452 27
                $sub             = stristr($this->host(), $rootDomain, true);
453 27
                $this->subDomain = $sub ? rtrim($sub, '.') : '';
454
            } else {
455
                $this->subDomain = '';
456
            }
457
        }
458
459 27
        return $this->subDomain;
460
    }
461
462
    /**
463
     * 设置当前泛域名的值
464
     * @access public
465
     * @param  string $domain 域名
466
     * @return $this
467
     */
468
    public function setPanDomain(string $domain)
469
    {
470
        $this->panDomain = $domain;
471
        return $this;
472
    }
473
474
    /**
475
     * 获取当前泛域名的值
476
     * @access public
477
     * @return string
478
     */
479 3
    public function panDomain(): string
480
    {
481 3
        return $this->panDomain ?: '';
482
    }
483
484
    /**
485
     * 设置当前完整URL 包括QUERY_STRING
486
     * @access public
487
     * @param  string $url URL地址
488
     * @return $this
489
     */
490
    public function setUrl(string $url)
491
    {
492
        $this->url = $url;
493
        return $this;
494
    }
495
496
    /**
497
     * 获取当前完整URL 包括QUERY_STRING
498
     * @access public
499
     * @param  bool $complete 是否包含完整域名
500
     * @return string
501
     */
502
    public function url(bool $complete = false): string
503
    {
504
        if ($this->url) {
505
            $url = $this->url;
506
        } elseif ($this->server('HTTP_X_REWRITE_URL')) {
507
            $url = $this->server('HTTP_X_REWRITE_URL');
508
        } elseif ($this->server('REQUEST_URI')) {
509
            $url = $this->server('REQUEST_URI');
510
        } elseif ($this->server('ORIG_PATH_INFO')) {
511
            $url = $this->server('ORIG_PATH_INFO') . (!empty($this->server('QUERY_STRING')) ? '?' . $this->server('QUERY_STRING') : '');
512
        } elseif (isset($_SERVER['argv'][1])) {
513
            $url = $_SERVER['argv'][1];
514
        } else {
515
            $url = '';
516
        }
517
518
        return $complete ? $this->domain() . $url : $url;
519
    }
520
521
    /**
522
     * 设置当前URL 不含QUERY_STRING
523
     * @access public
524
     * @param  string $url URL地址
525
     * @return $this
526
     */
527
    public function setBaseUrl(string $url)
528
    {
529
        $this->baseUrl = $url;
530
        return $this;
531
    }
532
533
    /**
534
     * 获取当前URL 不含QUERY_STRING
535
     * @access public
536
     * @param  bool $complete 是否包含完整域名
537
     * @return string
538
     */
539
    public function baseUrl(bool $complete = false): string
540
    {
541
        if (!$this->baseUrl) {
542
            $str           = $this->url();
543
            $this->baseUrl = str_contains($str, '?') ? strstr($str, '?', true) : $str;
544
        }
545
546
        return $complete ? $this->domain() . $this->baseUrl : $this->baseUrl;
547
    }
548
549
    /**
550
     * 获取当前执行的文件 SCRIPT_NAME
551
     * @access public
552
     * @param  bool $complete 是否包含完整域名
553
     * @return string
554
     */
555
    public function baseFile(bool $complete = false): string
556
    {
557
        if (!$this->baseFile) {
558
            $url = '';
559
            if (!$this->isCli()) {
560
                $script_name = basename($this->server('SCRIPT_FILENAME'));
561
                if (basename($this->server('SCRIPT_NAME')) === $script_name) {
562
                    $url = $this->server('SCRIPT_NAME');
563
                } elseif (basename($this->server('PHP_SELF')) === $script_name) {
564
                    $url = $this->server('PHP_SELF');
565
                } elseif (basename($this->server('ORIG_SCRIPT_NAME')) === $script_name) {
566
                    $url = $this->server('ORIG_SCRIPT_NAME');
567
                } elseif (($pos = strpos($this->server('PHP_SELF'), '/' . $script_name)) !== false) {
568
                    $url = substr($this->server('SCRIPT_NAME'), 0, $pos) . '/' . $script_name;
569
                } elseif ($this->server('DOCUMENT_ROOT') && str_starts_with($this->server('SCRIPT_FILENAME'), $this->server('DOCUMENT_ROOT'))) {
570
                    $url = str_replace('\\', '/', str_replace($this->server('DOCUMENT_ROOT'), '', $this->server('SCRIPT_FILENAME')));
571
                }
572
            }
573
            $this->baseFile = $url;
574
        }
575
576
        return $complete ? $this->domain() . $this->baseFile : $this->baseFile;
577
    }
578
579
    /**
580
     * 设置URL访问根地址
581
     * @access public
582
     * @param  string $url URL地址
583
     * @return $this
584
     */
585
    public function setRoot(string $url)
586
    {
587
        $this->root = $url;
588
        return $this;
589
    }
590
591
    /**
592
     * 获取URL访问根地址
593
     * @access public
594
     * @param  bool $complete 是否包含完整域名
595
     * @return string
596
     */
597
    public function root(bool $complete = false): string
598
    {
599
        if (!$this->root) {
600
            $file = $this->baseFile();
601
            if ($file && !str_starts_with($this->url(), $file)) {
602
                $file = str_replace('\\', '/', dirname($file));
603
            }
604
            $this->root = rtrim($file, '/');
605
        }
606
607
        return $complete ? $this->domain() . $this->root : $this->root;
608
    }
609
610
    /**
611
     * 获取URL访问根目录
612
     * @access public
613
     * @return string
614
     */
615
    public function rootUrl(): string
616
    {
617
        $base = $this->root();
618
        $root = str_contains($base, '.') ? ltrim(dirname($base), DIRECTORY_SEPARATOR) : $base;
619
620
        if ('' != $root) {
621
            $root = '/' . ltrim($root, '/');
622
        }
623
624
        return $root;
625
    }
626
627
    /**
628
     * 设置当前请求的pathinfo
629
     * @access public
630
     * @param  string $pathinfo
631
     * @return $this
632
     */
633
    public function setPathinfo(string $pathinfo)
634
    {
635
        $this->pathinfo = $pathinfo;
636
        return $this;
637
    }
638
639
    /**
640
     * 获取当前请求URL的pathinfo信息(含URL后缀)
641
     * @access public
642
     * @return string
643
     */
644
    public function pathinfo(): string
645
    {
646
        if (is_null($this->pathinfo)) {
0 ignored issues
show
introduced by
The condition is_null($this->pathinfo) is always false.
Loading history...
647
            if (isset($_GET[$this->varPathinfo])) {
648
                // 判断URL里面是否有兼容模式参数
649
                $pathinfo = $_GET[$this->varPathinfo];
650
                unset($_GET[$this->varPathinfo]);
651
                unset($this->get[$this->varPathinfo]);
652
            } elseif ($this->server('PATH_INFO')) {
653
                $pathinfo = $this->server('PATH_INFO');
654
            } elseif (str_contains(PHP_SAPI, 'cli')) {
655
                $pathinfo = str_contains($this->server('REQUEST_URI'), '?') ? strstr($this->server('REQUEST_URI'), '?', true) : $this->server('REQUEST_URI');
656
            }
657
658
            // 分析PATHINFO信息
659
            if (!isset($pathinfo)) {
660
                foreach ($this->pathinfoFetch as $type) {
661
                    if ($this->server($type)) {
662
                        $pathinfo = str_starts_with($this->server($type), $this->server('SCRIPT_NAME')) ?
663
                            substr($this->server($type), strlen($this->server('SCRIPT_NAME'))) : $this->server($type);
664
                        break;
665
                    }
666
                }
667
            }
668
669
            if (!empty($pathinfo)) {
670
                unset($this->get[$pathinfo], $this->request[$pathinfo]);
671
            }
672
673
            $this->pathinfo = empty($pathinfo) || '/' == $pathinfo ? '' : ltrim($pathinfo, '/');
674
        }
675
676
        return $this->pathinfo;
677
    }
678
679
    /**
680
     * 当前URL的访问后缀
681
     * @access public
682
     * @return string
683
     */
684
    public function ext(): string
685
    {
686
        return pathinfo($this->pathinfo(), PATHINFO_EXTENSION);
687
    }
688
689
    /**
690
     * 获取当前请求的时间
691
     * @access public
692
     * @param  bool $float 是否使用浮点类型
693
     * @return integer|float
694
     */
695
    public function time(bool $float = false)
696
    {
697
        return $float ? $this->server('REQUEST_TIME_FLOAT') : $this->server('REQUEST_TIME');
698
    }
699
700
    /**
701
     * 当前请求的资源类型
702
     * @access public
703
     * @return string
704
     */
705 21
    public function type(): string
706
    {
707 21
        $accept = $this->server('HTTP_ACCEPT');
708
709 21
        if (empty($accept)) {
710 21
            return '';
711
        }
712
713
        foreach ($this->mimeType as $key => $val) {
714
            $array = explode(',', $val);
715
            foreach ($array as $k => $v) {
716
                if (stristr($accept, $v)) {
717
                    return $key;
718
                }
719
            }
720
        }
721
722
        return '';
723
    }
724
725
    /**
726
     * 设置资源类型
727
     * @access public
728
     * @param  string|array $type 资源类型名
729
     * @param  string       $val 资源类型
730
     * @return void
731
     */
732
    public function mimeType($type, $val = ''): void
733
    {
734
        if (is_array($type)) {
735
            $this->mimeType = array_merge($this->mimeType, $type);
736
        } else {
737
            $this->mimeType[$type] = $val;
738
        }
739
    }
740
741
    /**
742
     * 设置请求类型
743
     * @access public
744
     * @param  string $method 请求类型
745
     * @return $this
746
     */
747
    public function setMethod(string $method)
748
    {
749
        $this->method = strtoupper($method);
750
        return $this;
751
    }
752
753
    /**
754
     * 当前的请求类型
755
     * @access public
756
     * @param  bool $origin 是否获取原始请求类型
757
     * @return string
758
     */
759
    public function method(bool $origin = false): string
760
    {
761
        if ($origin) {
762
            // 获取原始请求类型
763
            return $this->server('REQUEST_METHOD') ?: 'GET';
764
        } elseif (!$this->method) {
765
            if (isset($this->post[$this->varMethod])) {
766
                $method = strtolower($this->post[$this->varMethod]);
767
                if (in_array($method, ['get', 'post', 'put', 'patch', 'delete'])) {
768
                    $this->method    = strtoupper($method);
769
                    $this->{$method} = $this->post;
770
                } else {
771
                    $this->method = 'POST';
772
                }
773
                unset($this->post[$this->varMethod]);
774
            } elseif ($this->server('HTTP_X_HTTP_METHOD_OVERRIDE')) {
775
                $this->method = strtoupper($this->server('HTTP_X_HTTP_METHOD_OVERRIDE'));
776
            } else {
777
                $this->method = $this->server('REQUEST_METHOD') ?: 'GET';
778
            }
779
        }
780
781
        return $this->method;
782
    }
783
784
    /**
785
     * 是否为GET请求
786
     * @access public
787
     * @return bool
788
     */
789
    public function isGet(): bool
790
    {
791
        return $this->method() == 'GET';
792
    }
793
794
    /**
795
     * 是否为POST请求
796
     * @access public
797
     * @return bool
798
     */
799
    public function isPost(): bool
800
    {
801
        return $this->method() == 'POST';
802
    }
803
804
    /**
805
     * 是否为PUT请求
806
     * @access public
807
     * @return bool
808
     */
809
    public function isPut(): bool
810
    {
811
        return $this->method() == 'PUT';
812
    }
813
814
    /**
815
     * 是否为DELTE请求
816
     * @access public
817
     * @return bool
818
     */
819
    public function isDelete(): bool
820
    {
821
        return $this->method() == 'DELETE';
822
    }
823
824
    /**
825
     * 是否为HEAD请求
826
     * @access public
827
     * @return bool
828
     */
829
    public function isHead(): bool
830
    {
831
        return $this->method() == 'HEAD';
832
    }
833
834
    /**
835
     * 是否为PATCH请求
836
     * @access public
837
     * @return bool
838
     */
839
    public function isPatch(): bool
840
    {
841
        return $this->method() == 'PATCH';
842
    }
843
844
    /**
845
     * 是否为OPTIONS请求
846
     * @access public
847
     * @return bool
848
     */
849
    public function isOptions(): bool
850
    {
851
        return $this->method() == 'OPTIONS';
852
    }
853
854
    /**
855
     * 是否为cli
856
     * @access public
857
     * @return bool
858
     */
859
    public function isCli(): bool
860
    {
861
        return PHP_SAPI == 'cli';
862
    }
863
864
    /**
865
     * 是否为cgi
866
     * @access public
867
     * @return bool
868
     */
869
    public function isCgi(): bool
870
    {
871
        return str_starts_with(PHP_SAPI, 'cgi');
872
    }
873
874
    /**
875
     * 获取当前请求的参数
876
     * @access public
877
     * @param  string|array $name 变量名
878
     * @param  mixed        $default 默认值
879
     * @param  string|array $filter 过滤方法
880
     * @return mixed
881
     */
882 27
    public function param($name = '', $default = null, $filter = '')
883
    {
884 27
        if (empty($this->mergeParam)) {
885 27
            $method = $this->method(true);
886
887
            // 自动获取请求变量
888 27
            $vars   =   match ($method) {
889 27
                'POST'  =>  $this->post(false),
890 27
                'PUT','DELETE','PATCH'  =>  $this->put(false),
891 27
                default =>  [],
892 27
            };
893
894
            // 当前请求参数和URL地址中的参数合并
895 27
            $this->param = array_merge($this->param, $this->get(false), $vars, $this->route(false));
0 ignored issues
show
Bug introduced by
It seems like $this->get(false) can also be of type null and object; however, parameter $arrays of array_merge() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

895
            $this->param = array_merge($this->param, /** @scrutinizer ignore-type */ $this->get(false), $vars, $this->route(false));
Loading history...
896
897 27
            $this->mergeParam = true;
898
        }
899
900 27
        if (is_array($name)) {
901
            return $this->only($name, $this->param, $filter);
902
        }
903
904 27
        return $this->input($this->param, $name, $default, $filter);
905
    }
906
907
    /**
908
     * 获取包含文件在内的请求参数
909
     * @access public
910
     * @param  string|array $name 变量名
911
     * @param  string|array $filter 过滤方法
912
     * @return mixed
913
     */
914
    public function all(string|array $name = '', string|array $filter = '')
915
    {
916
        $data = array_merge($this->param(), $this->file() ?: []);
0 ignored issues
show
Bug introduced by
It seems like $this->file() ?: array() can also be of type think\file\UploadedFile; however, parameter $arrays of array_merge() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

916
        $data = array_merge($this->param(), /** @scrutinizer ignore-type */ $this->file() ?: []);
Loading history...
Bug introduced by
It seems like $this->param() can also be of type null and object; however, parameter $arrays of array_merge() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

916
        $data = array_merge(/** @scrutinizer ignore-type */ $this->param(), $this->file() ?: []);
Loading history...
917
918
        if (is_array($name)) {
919
            $data = $this->only($name, $data, $filter);
920
        } elseif ($name) {
921
            $data = $data[$name] ?? null;
922
        }
923
924
        return $data;
925
    }
926
927
    /**
928
     * 设置路由变量
929
     * @access public
930
     * @param  Rule $rule 路由对象
931
     * @return $this
932
     */
933 27
    public function setRule(Rule $rule)
934
    {
935 27
        $this->rule = $rule;
936 27
        return $this;
937
    }
938
939
    /**
940
     * 获取当前路由对象
941
     * @access public
942
     * @return Rule|null
943
     */
944 3
    public function rule()
945
    {
946 3
        return $this->rule;
947
    }
948
949
    /**
950
     * 设置路由变量
951
     * @access public
952
     * @param  array $route 路由变量
953
     * @return $this
954
     */
955 27
    public function setRoute(array $route)
956
    {
957 27
        $this->route      = array_merge($this->route, $route);
958 27
        $this->mergeParam = false;
959 27
        return $this;
960
    }
961
962
    /**
963
     * 获取路由参数
964
     * @access public
965
     * @param  string|array|bool $name 变量名
966
     * @param  mixed        $default 默认值
967
     * @param  string|array $filter 过滤方法
968
     * @return mixed
969
     */
970 27
    public function route(string|array|bool $name = '', $default = null, string|array $filter = '')
971
    {
972 27
        if (is_array($name)) {
973
            return $this->only($name, $this->route, $filter);
974
        }
975
976 27
        return $this->input($this->route, $name, $default, $filter);
977
    }
978
979
    /**
980
     * 获取GET参数
981
     * @access public
982
     * @param  string|array|bool $name 变量名
983
     * @param  mixed        $default 默认值
984
     * @param  string|array $filter 过滤方法
985
     * @return mixed
986
     */
987 27
    public function get(string|array|bool $name = '', $default = null, string|array $filter = '')
988
    {
989 27
        if (is_array($name)) {
990
            return $this->only($name, $this->get, $filter);
991
        }
992
993 27
        return $this->input($this->get, $name, $default, $filter);
994
    }
995
996
    /**
997
     * 获取中间件传递的参数
998
     * @access public
999
     * @param  string $name 变量名
1000
     * @param  mixed $default 默认值
1001
     * @return mixed
1002
     */
1003
    public function middleware(string $name, $default = null)
1004
    {
1005
        return $this->middleware[$name] ?? $default;
1006
    }
1007
1008
    /**
1009
     * 获取POST参数
1010
     * @access public
1011
     * @param  bool|string|array $name 变量名
1012
     * @param  mixed        $default 默认值
1013
     * @param  string|array $filter 过滤方法
1014
     * @return mixed
1015
     */
1016 3
    public function post(string|array|bool $name = '', $default = null, string|array $filter = '')
1017
    {
1018 3
        if (is_array($name)) {
1019
            return $this->only($name, $this->post, $filter);
1020
        }
1021
1022 3
        return $this->input($this->post, $name, $default, $filter);
1023
    }
1024
1025
    /**
1026
     * 获取PUT参数
1027
     * @access public
1028
     * @param  string|array|bool $name 变量名
1029
     * @param  mixed        $default 默认值
1030
     * @param  string|array $filter 过滤方法
1031
     * @return mixed
1032
     */
1033
    public function put(string|array|bool $name = '', $default = null, string|array $filter = '')
1034
    {
1035
        if (is_array($name)) {
1036
            return $this->only($name, $this->put, $filter);
1037
        }
1038
1039
        return $this->input($this->put, $name, $default, $filter);
1040
    }
1041
1042 27
    protected function getInputData(string $content): array
1043
    {
1044 27
        $contentType = $this->contentType();
1045 27
        if ('application/x-www-form-urlencoded' == $contentType) {
1046
            parse_str($content, $data);
1047
            return $data;
1048 27
        } elseif (str_contains($contentType, 'json')) {
1049
            return (array) json_decode($content, true);
1050
        }
1051
1052 27
        return [];
1053
    }
1054
1055
    /**
1056
     * 设置获取DELETE参数
1057
     * @access public
1058
     * @param  mixed        $name 变量名
1059
     * @param  mixed        $default 默认值
1060
     * @param  string|array $filter 过滤方法
1061
     * @return mixed
1062
     */
1063
    public function delete(string|array|bool $name = '', $default = null, string|array $filter = '')
1064
    {
1065
        return $this->put($name, $default, $filter);
1066
    }
1067
1068
    /**
1069
     * 设置获取PATCH参数
1070
     * @access public
1071
     * @param  mixed        $name 变量名
1072
     * @param  mixed        $default 默认值
1073
     * @param  string|array $filter 过滤方法
1074
     * @return mixed
1075
     */
1076
    public function patch(string|array|bool $name = '', $default = null, string|array $filter = '')
1077
    {
1078
        return $this->put($name, $default, $filter);
1079
    }
1080
1081
    /**
1082
     * 获取request变量
1083
     * @access public
1084
     * @param  string|array $name 数据名称
1085
     * @param  mixed        $default 默认值
1086
     * @param  string|array $filter 过滤方法
1087
     * @return mixed
1088
     */
1089
    public function request(string|array|bool $name = '', $default = null, string|array $filter = '')
1090
    {
1091
        if (is_array($name)) {
1092
            return $this->only($name, $this->request, $filter);
1093
        }
1094
1095
        return $this->input($this->request, $name, $default, $filter);
1096
    }
1097
1098
    /**
1099
     * 获取环境变量
1100
     * @access public
1101
     * @param  string $name 数据名称
1102
     * @param  string $default 默认值
1103
     * @return mixed
1104
     */
1105
    public function env(string $name = '', string $default = null)
1106
    {
1107
        if (empty($name)) {
1108
            return $this->env->get();
1109
        }
1110
        return $this->env->get(strtoupper($name), $default);
1111
    }
1112
1113
    /**
1114
     * 获取session数据
1115
     * @access public
1116
     * @param  string $name 数据名称
1117
     * @param  string $default 默认值
1118
     * @return mixed
1119
     */
1120
    public function session(string $name = '', $default = null)
1121
    {
1122
        if ('' === $name) {
1123
            return $this->session->all();
0 ignored issues
show
Bug introduced by
The method all() does not exist on think\Session. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

1123
            return $this->session->/** @scrutinizer ignore-call */ all();
Loading history...
1124
        }
1125
        return $this->session->get($name, $default);
0 ignored issues
show
Bug introduced by
The method get() does not exist on think\Session. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

1125
        return $this->session->/** @scrutinizer ignore-call */ get($name, $default);
Loading history...
1126
    }
1127
1128
    /**
1129
     * 获取cookie参数
1130
     * @access public
1131
     * @param  mixed        $name 数据名称
1132
     * @param  string       $default 默认值
1133
     * @param  string|array $filter 过滤方法
1134
     * @return mixed
1135
     */
1136
    public function cookie(string $name = '', $default = null, $filter = '')
1137
    {
1138
        if (!empty($name)) {
1139
            $data = $this->getData($this->cookie, $name, $default);
1140
        } else {
1141
            $data = $this->cookie;
1142
        }
1143
1144
        // 解析过滤器
1145
        $filter = $this->getFilter($filter, $default);
1146
1147
        if (is_array($data)) {
1148
            array_walk_recursive($data, [$this, 'filterValue'], $filter);
1149
        } else {
1150
            $this->filterValue($data, $name, $filter);
1151
        }
1152
1153
        return $data;
1154
    }
1155
1156
    /**
1157
     * 获取server参数
1158
     * @access public
1159
     * @param  string $name 数据名称
1160
     * @param  string $default 默认值
1161
     * @return mixed
1162
     */
1163 21
    public function server(string $name = '', string $default = '')
1164
    {
1165 21
        if (empty($name)) {
1166
            return $this->server;
1167
        }
1168 21
        return $this->server[strtoupper($name)] ?? $default;
1169
    }
1170
1171
    /**
1172
     * 获取上传的文件信息
1173
     * @access public
1174
     * @param  string $name 名称
1175
     * @return null|array|UploadedFile
1176
     */
1177
    public function file(string $name = '')
1178
    {
1179
        $files = $this->file;
1180
        if (!empty($files)) {
1181
            if (str_contains($name, '.')) {
1182
                [$name, $sub] = explode('.', $name);
1183
            }
1184
1185
            // 处理上传文件
1186
            $array = $this->dealUploadFile($files, $name);
1187
1188
            if ('' === $name) {
1189
                // 获取全部文件
1190
                return $array;
1191
            } elseif (isset($sub) && isset($array[$name][$sub])) {
1192
                return $array[$name][$sub];
1193
            } elseif (isset($array[$name])) {
1194
                return $array[$name];
1195
            }
1196
        }
1197
    }
1198
1199
    protected function dealUploadFile(array $files, string $name): array
1200
    {
1201
        $array = [];
1202
        foreach ($files as $key => $file) {
1203
            if (is_array($file['name'])) {
1204
                $item  = [];
1205
                $keys  = array_keys($file);
1206
                $count = count($file['name']);
1207
1208
                for ($i = 0; $i < $count; $i++) {
1209
                    if ($file['error'][$i] > 0) {
1210
                        if ($name == $key) {
1211
                            $this->throwUploadFileError($file['error'][$i]);
1212
                        } else {
1213
                            continue;
1214
                        }
1215
                    }
1216
1217
                    $temp['key'] = $key;
1218
1219
                    foreach ($keys as $_key) {
1220
                        $temp[$_key] = $file[$_key][$i];
1221
                    }
1222
1223
                    $item[] = new UploadedFile($temp['tmp_name'], $temp['name'], $temp['type'], $temp['error']);
1224
                }
1225
1226
                $array[$key] = $item;
1227
            } else {
1228
                if ($file instanceof File) {
1229
                    $array[$key] = $file;
1230
                } else {
1231
                    if ($file['error'] > 0) {
1232
                        if ($key == $name) {
1233
                            $this->throwUploadFileError($file['error']);
1234
                        } else {
1235
                            continue;
1236
                        }
1237
                    }
1238
1239
                    $array[$key] = new UploadedFile($file['tmp_name'], $file['name'], $file['type'], $file['error']);
1240
                }
1241
            }
1242
        }
1243
1244
        return $array;
1245
    }
1246
1247
    protected function throwUploadFileError($error)
1248
    {
1249
        static $fileUploadErrors = [
1250
            1 => 'upload File size exceeds the maximum value',
1251
            2 => 'upload File size exceeds the maximum value',
1252
            3 => 'only the portion of file is uploaded',
1253
            4 => 'no file to uploaded',
1254
            6 => 'upload temp dir not found',
1255
            7 => 'file write error',
1256
        ];
1257
1258
        $msg = Lang::get($fileUploadErrors[$error]);
0 ignored issues
show
Bug introduced by
The method get() does not exist on think\facade\Lang. Since you implemented __callStatic, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

1258
        /** @scrutinizer ignore-call */ 
1259
        $msg = Lang::get($fileUploadErrors[$error]);
Loading history...
1259
        throw new Exception($msg, $error);
1260
    }
1261
1262
    /**
1263
     * 设置或者获取当前的Header
1264
     * @access public
1265
     * @param  string $name header名称
1266
     * @param  string $default 默认值
1267
     * @return string|array|null
1268
     */
1269 27
    public function header(string $name = '', string $default = null)
1270
    {
1271 27
        if ('' === $name) {
1272
            return $this->header;
1273
        }
1274
1275 27
        $name = str_replace('_', '-', strtolower($name));
1276 27
        return $this->header[$name] ?? $default;
1277
    }
1278
1279
    /**
1280
     * 获取变量 支持过滤和默认值
1281
     * @access public
1282
     * @param  array        $data 数据源
1283
     * @param  string|false $name 字段名
1284
     * @param  mixed        $default 默认值
1285
     * @param  string|array $filter 过滤函数
1286
     * @return mixed
1287
     */
1288 27
    public function input(array $data = [], string|bool $name = '', $default = null, string|array $filter = '')
1289
    {
1290 27
        if (false === $name) {
1291
            // 获取原始数据
1292 27
            return $data;
1293
        }
1294
1295 27
        $name = (string) $name;
1296 27
        if ('' != $name) {
1297
            // 解析name
1298
            if (str_contains($name, '/')) {
1299
                [$name, $type] = explode('/', $name);
1300
            }
1301
1302
            $data = $this->getData($data, $name);
1303
1304
            if (is_null($data)) {
1305
                return $default;
1306
            }
1307
1308
            if (is_object($data)) {
1309
                return $data;
1310
            }
1311
        }
1312
1313 27
        $data = $this->filterData($data, $filter, $name, $default);
1314
1315 27
        if (isset($type) && $data !== $default) {
1316
            // 强制类型转换
1317
            $this->typeCast($data, $type);
1318
        }
1319
1320 27
        return $data;
1321
    }
1322
1323 27
    protected function filterData($data, $filter, $name, $default)
1324
    {
1325
        // 解析过滤器
1326 27
        $filter = $this->getFilter($filter, $default);
1327
1328 27
        if (is_array($data)) {
1329 27
            array_walk_recursive($data, [$this, 'filterValue'], $filter);
1330
        } else {
1331
            $this->filterValue($data, $name, $filter);
1332
        }
1333
1334 27
        return $data;
1335
    }
1336
1337
    /**
1338
     * 强制类型转换
1339
     * @access protected
1340
     * @param  mixed  $data
1341
     * @param  string $type
1342
     * @return mixed
1343
     */
1344
    protected function typeCast(&$data, string $type)
1345
    {
1346
        $type = strtolower($type);
1347
        if (in_array($type, ['a', 'b', 'd', 'f', 's'])) {
1348
            $data   =   match ($type) {
1349
                'a'     =>  (array) $data,  // 数组
1350
                'b'     =>  (bool) $data,   // 布尔
1351
                'd'     =>  (int) $data,    // 数字
1352
                'f'     =>  (float) $data,  // 浮点
1353
                's'     =>  is_scalar($data) ? (string) $data : throw new \InvalidArgumentException('variable type error:' . gettype($data)), //字符串
1354
            };
1355
        }
1356
    }
1357
1358
    /**
1359
     * 获取数据
1360
     * @access protected
1361
     * @param  array  $data 数据源
1362
     * @param  string $name 字段名
1363
     * @param  mixed  $default 默认值
1364
     * @return mixed
1365
     */
1366
    protected function getData(array $data, string $name, $default = null)
1367
    {
1368
        foreach (explode('.', $name) as $val) {
1369
            if (isset($data[$val])) {
1370
                $data = $data[$val];
1371
            } else {
1372
                return $default;
1373
            }
1374
        }
1375
1376
        return $data;
1377
    }
1378
1379
    /**
1380
     * 设置或获取当前的过滤规则
1381
     * @access public
1382
     * @param  mixed $filter 过滤规则
1383
     * @return mixed
1384
     */
1385
    public function filter($filter = null)
1386
    {
1387
        if (is_null($filter)) {
1388
            return $this->filter;
1389
        }
1390
1391
        $this->filter = $filter;
1392
1393
        return $this;
1394
    }
1395
1396 27
    protected function getFilter($filter, $default): array
1397
    {
1398 27
        if (is_null($filter)) {
1399
            $filter = [];
1400
        } else {
1401 27
            $filter = $filter ?: $this->filter;
1402 27
            if (is_string($filter) && !str_contains($filter, '/')) {
1403
                $filter = explode(',', $filter);
1404
            } else {
1405 27
                $filter = (array) $filter;
1406
            }
1407
        }
1408
1409 27
        $filter[] = $default;
1410
1411 27
        return $filter;
1412
    }
1413
1414
    /**
1415
     * 递归过滤给定的值
1416
     * @access public
1417
     * @param  mixed $value 键值
1418
     * @param  mixed $key 键名
1419
     * @param  array $filters 过滤方法+默认值
1420
     * @return mixed
1421
     */
1422
    public function filterValue(&$value, $key, $filters)
1423
    {
1424
        $default = array_pop($filters);
1425
1426
        foreach ($filters as $filter) {
1427
            if (is_callable($filter)) {
1428
                // 调用函数或者方法过滤
1429
                if (is_null($value)) {
1430
                    continue;
1431
                }
1432
1433
                $value = call_user_func($filter, $value);
1434
            } elseif (is_scalar($value)) {
1435
                if (is_string($filter) && str_contains($filter, '/')) {
1436
                    // 正则过滤
1437
                    if (!preg_match($filter, $value)) {
1438
                        // 匹配不成功返回默认值
1439
                        $value = $default;
1440
                        break;
1441
                    }
1442
                } elseif (!empty($filter)) {
1443
                    // filter函数不存在时, 则使用filter_var进行过滤
1444
                    // filter为非整形值时, 调用filter_id取得过滤id
1445
                    $value = filter_var($value, is_int($filter) ? $filter : filter_id($filter));
1446
                    if (false === $value) {
1447
                        $value = $default;
1448
                        break;
1449
                    }
1450
                }
1451
            }
1452
        }
1453
1454
        return $value;
1455
    }
1456
1457
    /**
1458
     * 是否存在某个请求参数
1459
     * @access public
1460
     * @param  string $name 变量名
1461
     * @param  string $type 变量类型
1462
     * @param  bool   $checkEmpty 是否检测空值
1463
     * @return bool
1464
     */
1465
    public function has(string $name, string $type = 'param', bool $checkEmpty = false): bool
1466
    {
1467
        if (!in_array($type, ['param', 'get', 'post', 'put', 'patch', 'route', 'delete', 'cookie', 'session', 'env', 'request', 'server', 'header', 'file'])) {
1468
            return false;
1469
        }
1470
1471
        $param = empty($this->$type) ? $this->$type() : $this->$type;
1472
1473
        if (is_object($param)) {
1474
            return $param->has($name);
1475
        }
1476
1477
        // 按.拆分成多维数组进行判断
1478
        foreach (explode('.', $name) as $val) {
1479
            if (isset($param[$val])) {
1480
                $param = $param[$val];
1481
            } else {
1482
                return false;
1483
            }
1484
        }
1485
1486
        return ($checkEmpty && '' === $param) ? false : true;
1487
    }
1488
1489
    /**
1490
     * 获取指定的参数
1491
     * @access public
1492
     * @param  array        $name 变量名
1493
     * @param  mixed        $data 数据或者变量类型
1494
     * @param  string|array $filter 过滤方法
1495
     * @return array
1496
     */
1497
    public function only(array $name, $data = 'param', $filter = ''): array
1498
    {
1499
        $data = is_array($data) ? $data : $this->$data();
1500
1501
        $item = [];
1502
        foreach ($name as $key => $val) {
1503
1504
            if (is_int($key)) {
1505
                $default = null;
1506
                $key     = $val;
1507
                if (!key_exists($key, $data)) {
1508
                    continue;
1509
                }
1510
            } else {
1511
                $default = $val;
1512
            }
1513
1514
            $item[$key] = $this->filterData($data[$key] ?? $default, $filter, $key, $default);
1515
        }
1516
1517
        return $item;
1518
    }
1519
1520
    /**
1521
     * 排除指定参数获取
1522
     * @access public
1523
     * @param  array  $name 变量名
1524
     * @param  string $type 变量类型
1525
     * @return mixed
1526
     */
1527
    public function except(array $name, string $type = 'param'): array
1528
    {
1529
        $param = $this->$type();
1530
1531
        foreach ($name as $key) {
1532
            if (isset($param[$key])) {
1533
                unset($param[$key]);
1534
            }
1535
        }
1536
1537
        return $param;
1538
    }
1539
1540
    /**
1541
     * 当前是否ssl
1542
     * @access public
1543
     * @return bool
1544
     */
1545
    public function isSsl(): bool
1546
    {
1547
        if ($this->server('HTTPS') && ('1' == $this->server('HTTPS') || 'on' == strtolower($this->server('HTTPS')))) {
1548
            return true;
1549
        } elseif ('https' == $this->server('REQUEST_SCHEME')) {
1550
            return true;
1551
        } elseif ('443' == $this->server('SERVER_PORT')) {
1552
            return true;
1553
        } elseif ('https' == $this->server('HTTP_X_FORWARDED_PROTO')) {
1554
            return true;
1555
        } elseif ($this->httpsAgentName && $this->server($this->httpsAgentName)) {
1556
            return true;
1557
        }
1558
1559
        return false;
1560
    }
1561
1562
    /**
1563
     * 当前是否JSON请求
1564
     * @access public
1565
     * @return bool
1566
     */
1567 21
    public function isJson(): bool
1568
    {
1569 21
        $acceptType = $this->type();
1570
1571 21
        return str_contains($acceptType, 'json');
1572
    }
1573
1574
    /**
1575
     * 当前是否Ajax请求
1576
     * @access public
1577
     * @param  bool $ajax true 获取原始ajax请求
1578
     * @return bool
1579
     */
1580
    public function isAjax(bool $ajax = false): bool
1581
    {
1582
        $value  = $this->server('HTTP_X_REQUESTED_WITH');
1583
        $result = $value && 'xmlhttprequest' == strtolower($value) ? true : false;
1584
1585
        if (true === $ajax) {
1586
            return $result;
1587
        }
1588
1589
        return $this->param($this->varAjax) ? true : $result;
1590
    }
1591
1592
    /**
1593
     * 当前是否Pjax请求
1594
     * @access public
1595
     * @param  bool $pjax true 获取原始pjax请求
1596
     * @return bool
1597
     */
1598
    public function isPjax(bool $pjax = false): bool
1599
    {
1600
        $result = !empty($this->server('HTTP_X_PJAX')) ? true : false;
1601
1602
        if (true === $pjax) {
1603
            return $result;
1604
        }
1605
1606
        return $this->param($this->varPjax) ? true : $result;
1607
    }
1608
1609
    /**
1610
     * 获取客户端IP地址
1611
     * @access public
1612
     * @return string
1613
     */
1614
    public function ip(): string
1615
    {
1616
        if (!empty($this->realIP)) {
1617
            return $this->realIP;
1618
        }
1619
1620
        $this->realIP = $this->server('REMOTE_ADDR', '');
1621
1622
        // 如果指定了前端代理服务器IP以及其会发送的IP头
1623
        // 则尝试获取前端代理服务器发送过来的真实IP
1624
        $proxyIp       = $this->proxyServerIp;
1625
        $proxyIpHeader = $this->proxyServerIpHeader;
1626
1627
        if (count($proxyIp) > 0 && count($proxyIpHeader) > 0) {
1628
            // 从指定的HTTP头中依次尝试获取IP地址
1629
            // 直到获取到一个合法的IP地址
1630
            foreach ($proxyIpHeader as $header) {
1631
                $tempIP = $this->server($header);
1632
1633
                if (empty($tempIP)) {
1634
                    continue;
1635
                }
1636
1637
                $tempIP = trim(explode(',', $tempIP)[0]);
0 ignored issues
show
Bug introduced by
It seems like $tempIP can also be of type array; however, parameter $string of explode() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1637
                $tempIP = trim(explode(',', /** @scrutinizer ignore-type */ $tempIP)[0]);
Loading history...
1638
1639
                if (!$this->isValidIP($tempIP)) {
1640
                    $tempIP = null;
1641
                } else {
1642
                    break;
1643
                }
1644
            }
1645
1646
            // tempIP不为空,说明获取到了一个IP地址
1647
            // 这时我们检查 REMOTE_ADDR 是不是指定的前端代理服务器之一
1648
            // 如果是的话说明该 IP头 是由前端代理服务器设置的
1649
            // 否则则是伪装的
1650
            if (!empty($tempIP)) {
1651
                $realIPBin = $this->ip2bin($this->realIP);
1652
1653
                foreach ($proxyIp as $ip) {
1654
                    $serverIPElements = explode('/', $ip);
1655
                    $serverIP         = $serverIPElements[0];
1656
                    $serverIPPrefix   = $serverIPElements[1] ?? 128;
1657
                    $serverIPBin      = $this->ip2bin($serverIP);
1658
1659
                    // IP类型不符
1660
                    if (strlen($realIPBin) !== strlen($serverIPBin)) {
1661
                        continue;
1662
                    }
1663
1664
                    if (strncmp($realIPBin, $serverIPBin, (int) $serverIPPrefix) === 0) {
1665
                        $this->realIP = $tempIP;
1666
                        break;
1667
                    }
1668
                }
1669
            }
1670
        }
1671
1672
        if (!$this->isValidIP($this->realIP)) {
1673
            $this->realIP = '0.0.0.0';
1674
        }
1675
1676
        return $this->realIP;
1677
    }
1678
1679
    /**
1680
     * 检测是否是合法的IP地址
1681
     *
1682
     * @param string $ip   IP地址
1683
     * @param string $type IP地址类型 (ipv4, ipv6)
1684
     *
1685
     * @return boolean
1686
     */
1687
    public function isValidIP(string $ip, string $type = ''): bool
1688
    {
1689
        $flag   =   match (strtolower($type)) {
1690
            'ipv4'  =>  FILTER_FLAG_IPV4,
1691
            'ipv6'  =>  FILTER_FLAG_IPV6,
1692
            default =>  0,
1693
        };
1694
1695
        return boolval(filter_var($ip, FILTER_VALIDATE_IP, $flag));
1696
    }
1697
1698
    /**
1699
     * 将IP地址转换为二进制字符串
1700
     *
1701
     * @param string $ip
1702
     *
1703
     * @return string
1704
     */
1705
    public function ip2bin(string $ip): string
1706
    {
1707
        if ($this->isValidIP($ip, 'ipv6')) {
1708
            $IPHex = str_split(bin2hex(inet_pton($ip)), 4);
1709
            foreach ($IPHex as $key => $value) {
1710
                $IPHex[$key] = intval($value, 16);
1711
            }
1712
            $IPBin = vsprintf('%016b%016b%016b%016b%016b%016b%016b%016b', $IPHex);
0 ignored issues
show
Bug introduced by
It seems like $IPHex can also be of type true; however, parameter $values of vsprintf() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1712
            $IPBin = vsprintf('%016b%016b%016b%016b%016b%016b%016b%016b', /** @scrutinizer ignore-type */ $IPHex);
Loading history...
1713
        } else {
1714
            $IPHex = str_split(bin2hex(inet_pton($ip)), 2);
1715
            foreach ($IPHex as $key => $value) {
1716
                $IPHex[$key] = intval($value, 16);
1717
            }
1718
            $IPBin = vsprintf('%08b%08b%08b%08b', $IPHex);
1719
        }
1720
1721
        return $IPBin;
1722
    }
1723
1724
    /**
1725
     * 检测是否使用手机访问
1726
     * @access public
1727
     * @return bool
1728
     */
1729
    public function isMobile(): bool
1730
    {
1731
        if ($this->server('HTTP_VIA') && stristr($this->server('HTTP_VIA'), "wap")) {
1732
            return true;
1733
        } elseif ($this->server('HTTP_ACCEPT') && str_contains(strtoupper($this->server('HTTP_ACCEPT')), "VND.WAP.WML")) {
1734
            return true;
1735
        } elseif ($this->server('HTTP_X_WAP_PROFILE') || $this->server('HTTP_PROFILE')) {
1736
            return true;
1737
        } elseif ($this->server('HTTP_USER_AGENT') && preg_match('/(blackberry|configuration\/cldc|hp |hp-|htc |htc_|htc-|iemobile|kindle|midp|mmp|motorola|mobile|nokia|opera mini|opera |Googlebot-Mobile|YahooSeeker\/M1A1-R2D2|android|iphone|ipod|mobi|palm|palmos|pocket|portalmmm|ppc;|smartphone|sonyericsson|sqh|spv|symbian|treo|up.browser|up.link|vodafone|windows ce|xda |xda_)/i', $this->server('HTTP_USER_AGENT'))) {
1738
            return true;
1739
        }
1740
1741
        return false;
1742
    }
1743
1744
    /**
1745
     * 当前URL地址中的scheme参数
1746
     * @access public
1747
     * @return string
1748
     */
1749
    public function scheme(): string
1750
    {
1751
        return $this->isSsl() ? 'https' : 'http';
1752
    }
1753
1754
    /**
1755
     * 当前请求URL地址中的query参数
1756
     * @access public
1757
     * @return string
1758
     */
1759
    public function query(): string
1760
    {
1761
        return $this->server('QUERY_STRING', '');
1762
    }
1763
1764
    /**
1765
     * 设置当前请求的host(包含端口)
1766
     * @access public
1767
     * @param  string $host 主机名(含端口)
1768
     * @return $this
1769
     */
1770
    public function setHost(string $host)
1771
    {
1772
        $this->host = $host;
1773
1774
        return $this;
1775
    }
1776
1777
    /**
1778
     * 当前请求的host
1779
     * @access public
1780
     * @param bool $strict  true 仅仅获取HOST
1781
     * @return string
1782
     */
1783
    public function host(bool $strict = false): string
1784
    {
1785
        if ($this->host) {
1786
            $host = $this->host;
1787
        } else {
1788
            $host = strval($this->server('HTTP_X_FORWARDED_HOST') ?: $this->server('HTTP_HOST'));
1789
        }
1790
1791
        return true === $strict && str_contains($host, ':') ? strstr($host, ':', true) : $host;
1792
    }
1793
1794
    /**
1795
     * 当前请求URL地址中的port参数
1796
     * @access public
1797
     * @return int
1798
     */
1799
    public function port(): int
1800
    {
1801
        return (int) ($this->server('HTTP_X_FORWARDED_PORT') ?: $this->server('SERVER_PORT', ''));
1802
    }
1803
1804
    /**
1805
     * 当前请求 SERVER_PROTOCOL
1806
     * @access public
1807
     * @return string
1808
     */
1809
    public function protocol(): string
1810
    {
1811
        return $this->server('SERVER_PROTOCOL', '');
1812
    }
1813
1814
    /**
1815
     * 当前请求 REMOTE_PORT
1816
     * @access public
1817
     * @return int
1818
     */
1819
    public function remotePort(): int
1820
    {
1821
        return (int) $this->server('REMOTE_PORT', '');
1822
    }
1823
1824
    /**
1825
     * 当前请求 HTTP_CONTENT_TYPE
1826
     * @access public
1827
     * @return string
1828
     */
1829 27
    public function contentType(): string
1830
    {
1831 27
        $contentType = $this->header('Content-Type');
1832
1833 27
        if ($contentType) {
1834
            if (str_contains($contentType, ';')) {
0 ignored issues
show
Bug introduced by
It seems like $contentType can also be of type array; however, parameter $haystack of str_contains() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1834
            if (str_contains(/** @scrutinizer ignore-type */ $contentType, ';')) {
Loading history...
1835
                [$type] = explode(';', $contentType);
0 ignored issues
show
Bug introduced by
It seems like $contentType can also be of type array; however, parameter $string of explode() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1835
                [$type] = explode(';', /** @scrutinizer ignore-type */ $contentType);
Loading history...
1836
            } else {
1837
                $type = $contentType;
1838
            }
1839
            return trim($type);
0 ignored issues
show
Bug introduced by
It seems like $type can also be of type array; however, parameter $string of trim() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1839
            return trim(/** @scrutinizer ignore-type */ $type);
Loading history...
1840
        }
1841
1842 27
        return '';
1843
    }
1844
1845
    /**
1846
     * 获取当前请求的安全Key
1847
     * @access public
1848
     * @return string
1849
     */
1850
    public function secureKey(): string
1851
    {
1852
        if (is_null($this->secureKey)) {
0 ignored issues
show
introduced by
The condition is_null($this->secureKey) is always false.
Loading history...
1853
            $this->secureKey = uniqid('', true);
1854
        }
1855
1856
        return $this->secureKey;
1857
    }
1858
1859
    /**
1860
     * 设置当前的控制器名
1861
     * @access public
1862
     * @param  string $controller 控制器名
1863
     * @return $this
1864
     */
1865 12
    public function setController(string $controller)
1866
    {
1867 12
        $this->controller = $controller;
1868 12
        return $this;
1869
    }
1870
1871
    /**
1872
     * 设置当前的操作名
1873
     * @access public
1874
     * @param  string $action 操作名
1875
     * @return $this
1876
     */
1877 12
    public function setAction(string $action)
1878
    {
1879 12
        $this->action = $action;
1880 12
        return $this;
1881
    }
1882
1883
    /**
1884
     * 获取当前的控制器名
1885
     * @access public
1886
     * @param  bool $convert 转换为小写
1887
     * @return string
1888
     */
1889
    public function controller(bool $convert = false): string
1890
    {
1891
        $name = $this->controller ?: '';
1892
        return $convert ? strtolower($name) : $name;
1893
    }
1894
1895
    /**
1896
     * 获取当前的操作名
1897
     * @access public
1898
     * @param  bool $convert 转换为小写
1899
     * @return string
1900
     */
1901 6
    public function action(bool $convert = false): string
1902
    {
1903 6
        $name = $this->action ?: '';
1904 6
        return $convert ? strtolower($name) : $name;
1905
    }
1906
1907
    /**
1908
     * 设置或者获取当前请求的content
1909
     * @access public
1910
     * @return string
1911
     */
1912
    public function getContent(): string
1913
    {
1914
        if (is_null($this->content)) {
0 ignored issues
show
introduced by
The condition is_null($this->content) is always false.
Loading history...
1915
            $this->content = $this->input;
1916
        }
1917
1918
        return $this->content;
1919
    }
1920
1921
    /**
1922
     * 获取当前请求的php://input
1923
     * @access public
1924
     * @return string
1925
     */
1926
    public function getInput(): string
1927
    {
1928
        return $this->input;
1929
    }
1930
1931
    /**
1932
     * 生成请求令牌
1933
     * @access public
1934
     * @param  string $name 令牌名称
1935
     * @param  mixed  $type 令牌生成方法
1936
     * @return string
1937
     */
1938
    public function buildToken(string $name = '__token__', $type = 'md5'): string
1939
    {
1940
        $type  = is_callable($type) ? $type : 'md5';
1941
        $token = call_user_func($type, $this->server('REQUEST_TIME_FLOAT'));
1942
1943
        $this->session->set($name, $token);
0 ignored issues
show
Bug introduced by
The method set() does not exist on think\Session. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

1943
        $this->session->/** @scrutinizer ignore-call */ 
1944
                        set($name, $token);
Loading history...
1944
1945
        return $token;
1946
    }
1947
1948
    /**
1949
     * 检查请求令牌
1950
     * @access public
1951
     * @param  string $token 令牌名称
1952
     * @param  array  $data  表单数据
1953
     * @return bool
1954
     */
1955
    public function checkToken(string $token = '__token__', array $data = []): bool
1956
    {
1957
        if (in_array($this->method(), ['GET', 'HEAD', 'OPTIONS'], true)) {
1958
            return true;
1959
        }
1960
1961
        if (!$this->session->has($token)) {
0 ignored issues
show
Bug introduced by
The method has() does not exist on think\Session. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

1961
        if (!$this->session->/** @scrutinizer ignore-call */ has($token)) {
Loading history...
1962
            // 令牌数据无效
1963
            return false;
1964
        }
1965
1966
        // Header验证
1967
        if ($this->header('X-CSRF-TOKEN') && $this->session->get($token) === $this->header('X-CSRF-TOKEN')) {
1968
            // 防止重复提交
1969
            $this->session->delete($token); // 验证完成销毁session
0 ignored issues
show
Bug introduced by
The method delete() does not exist on think\Session. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

1969
            $this->session->/** @scrutinizer ignore-call */ 
1970
                            delete($token); // 验证完成销毁session
Loading history...
1970
            return true;
1971
        }
1972
1973
        if (empty($data)) {
1974
            $data = $this->post();
1975
        }
1976
1977
        // 令牌验证
1978
        if (isset($data[$token]) && $this->session->get($token) === $data[$token]) {
1979
            // 防止重复提交
1980
            $this->session->delete($token); // 验证完成销毁session
1981
            return true;
1982
        }
1983
1984
        // 开启TOKEN重置
1985
        $this->session->delete($token);
1986
        return false;
1987
    }
1988
1989
    /**
1990
     * 设置在中间件传递的数据
1991
     * @access public
1992
     * @param  array $middleware 数据
1993
     * @return $this
1994
     */
1995
    public function withMiddleware(array $middleware)
1996
    {
1997
        $this->middleware = array_merge($this->middleware, $middleware);
1998
        return $this;
1999
    }
2000
2001
    /**
2002
     * 设置GET数据
2003
     * @access public
2004
     * @param  array $get 数据
2005
     * @return $this
2006
     */
2007
    public function withGet(array $get)
2008
    {
2009
        $this->get = $get;
2010
        return $this;
2011
    }
2012
2013
    /**
2014
     * 设置POST数据
2015
     * @access public
2016
     * @param  array $post 数据
2017
     * @return $this
2018
     */
2019
    public function withPost(array $post)
2020
    {
2021
        $this->post = $post;
2022
        return $this;
2023
    }
2024
2025
    /**
2026
     * 设置COOKIE数据
2027
     * @access public
2028
     * @param array $cookie 数据
2029
     * @return $this
2030
     */
2031
    public function withCookie(array $cookie)
2032
    {
2033
        $this->cookie = $cookie;
2034
        return $this;
2035
    }
2036
2037
    /**
2038
     * 设置SESSION数据
2039
     * @access public
2040
     * @param Session $session 数据
2041
     * @return $this
2042
     */
2043
    public function withSession(Session $session)
2044
    {
2045
        $this->session = $session;
2046
        return $this;
2047
    }
2048
2049
    /**
2050
     * 设置SERVER数据
2051
     * @access public
2052
     * @param  array $server 数据
2053
     * @return $this
2054
     */
2055
    public function withServer(array $server)
2056
    {
2057
        $this->server = array_change_key_case($server, CASE_UPPER);
2058
        return $this;
2059
    }
2060
2061
    /**
2062
     * 设置HEADER数据
2063
     * @access public
2064
     * @param  array $header 数据
2065
     * @return $this
2066
     */
2067
    public function withHeader(array $header)
2068
    {
2069
        $this->header = array_change_key_case($header);
2070
        return $this;
2071
    }
2072
2073
    /**
2074
     * 设置ENV数据
2075
     * @access public
2076
     * @param Env $env 数据
2077
     * @return $this
2078
     */
2079
    public function withEnv(Env $env)
2080
    {
2081
        $this->env = $env;
2082
        return $this;
2083
    }
2084
2085
    /**
2086
     * 设置php://input数据
2087
     * @access public
2088
     * @param string $input RAW数据
2089
     * @return $this
2090
     */
2091
    public function withInput(string $input)
2092
    {
2093
        $this->input = $input;
2094
        if (!empty($input)) {
2095
            $inputData = $this->getInputData($input);
2096
            if (!empty($inputData)) {
2097
                $this->post = $inputData;
2098
                $this->put  = $inputData;
2099
            }
2100
        }
2101
        return $this;
2102
    }
2103
2104
    /**
2105
     * 设置文件上传数据
2106
     * @access public
2107
     * @param  array $files 上传信息
2108
     * @return $this
2109
     */
2110
    public function withFiles(array $files)
2111
    {
2112
        $this->file = $files;
2113
        return $this;
2114
    }
2115
2116
    /**
2117
     * 设置ROUTE变量
2118
     * @access public
2119
     * @param  array $route 数据
2120
     * @return $this
2121
     */
2122
    public function withRoute(array $route)
2123
    {
2124
        $this->route = $route;
2125
        return $this;
2126
    }
2127
2128
    /**
2129
     * 设置中间传递数据
2130
     * @access public
2131
     * @param  string    $name  参数名
2132
     * @param  mixed     $value 值
2133
     */
2134
    public function __set(string $name, $value)
2135
    {
2136
        $this->middleware[$name] = $value;
2137
    }
2138
2139
    /**
2140
     * 获取中间传递数据的值
2141
     * @access public
2142
     * @param  string $name 名称
2143
     * @return mixed
2144
     */
2145
    public function __get(string $name)
2146
    {
2147
        return $this->middleware($name);
2148
    }
2149
2150
    /**
2151
     * 检测中间传递数据的值
2152
     * @access public
2153
     * @param  string $name 名称
2154
     * @return boolean
2155
     */
2156
    public function __isset(string $name): bool
2157
    {
2158
        return isset($this->middleware[$name]);
2159
    }
2160
2161
    // ArrayAccess
2162
    public function offsetExists(mixed $name): bool
2163
    {
2164
        return $this->has($name);
2165
    }
2166
2167
    public function offsetGet(mixed $name): mixed
2168
    {
2169
        return $this->param($name);
2170
    }
2171
2172
    public function offsetSet(mixed $name, mixed $value): void
2173
    {
2174
    }
2175
2176
    public function offsetUnset(mixed $name): void
2177
    {
2178
    }
2179
}
2180