ci4

Форк
0
/
RequestTrait.php 
379 строк · 11.5 Кб
1
<?php
2

3
declare(strict_types=1);
4

5
/**
6
 * This file is part of CodeIgniter 4 framework.
7
 *
8
 * (c) CodeIgniter Foundation <admin@codeigniter.com>
9
 *
10
 * For the full copyright and license information, please view
11
 * the LICENSE file that was distributed with this source code.
12
 */
13

14
namespace CodeIgniter\HTTP;
15

16
use CodeIgniter\Exceptions\ConfigException;
17
use CodeIgniter\Validation\FormatRules;
18
use Config\App;
19

20
/**
21
 * Request Trait
22
 *
23
 * Additional methods to make a PSR-7 Request class
24
 * compliant with the framework's own RequestInterface.
25
 *
26
 * @see https://github.com/php-fig/http-message/blob/master/src/RequestInterface.php
27
 */
28
trait RequestTrait
29
{
30
    /**
31
     * Configuration settings.
32
     *
33
     * @var App
34
     */
35
    protected $config;
36

37
    /**
38
     * IP address of the current user.
39
     *
40
     * @var string
41
     *
42
     * @deprecated Will become private in a future release
43
     */
44
    protected $ipAddress = '';
45

46
    /**
47
     * Stores values we've retrieved from PHP globals.
48
     *
49
     * @var array{get?: array, post?: array, request?: array, cookie?: array, server?: array}
50
     */
51
    protected $globals = [];
52

53
    /**
54
     * Gets the user's IP address.
55
     *
56
     * @return string IP address if it can be detected.
57
     *                If the IP address is not a valid IP address,
58
     *                then will return '0.0.0.0'.
59
     */
60
    public function getIPAddress(): string
61
    {
62
        if ($this->ipAddress) {
63
            return $this->ipAddress;
64
        }
65

66
        $ipValidator = [
67
            new FormatRules(),
68
            'valid_ip',
69
        ];
70

71
        $proxyIPs = $this->config->proxyIPs;
72

73
        if (! empty($proxyIPs) && (! is_array($proxyIPs) || is_int(array_key_first($proxyIPs)))) {
74
            throw new ConfigException(
75
                'You must set an array with Proxy IP address key and HTTP header name value in Config\App::$proxyIPs.'
76
            );
77
        }
78

79
        $this->ipAddress = $this->getServer('REMOTE_ADDR');
80

81
        // If this is a CLI request, $this->ipAddress is null.
82
        if ($this->ipAddress === null) {
83
            return $this->ipAddress = '0.0.0.0';
84
        }
85

86
        // @TODO Extract all this IP address logic to another class.
87
        foreach ($proxyIPs as $proxyIP => $header) {
88
            // Check if we have an IP address or a subnet
89
            if (! str_contains($proxyIP, '/')) {
90
                // An IP address (and not a subnet) is specified.
91
                // We can compare right away.
92
                if ($proxyIP === $this->ipAddress) {
93
                    $spoof = $this->getClientIP($header);
94

95
                    if ($spoof !== null) {
96
                        $this->ipAddress = $spoof;
97
                        break;
98
                    }
99
                }
100

101
                continue;
102
            }
103

104
            // We have a subnet ... now the heavy lifting begins
105
            if (! isset($separator)) {
106
                $separator = $ipValidator($this->ipAddress, 'ipv6') ? ':' : '.';
107
            }
108

109
            // If the proxy entry doesn't match the IP protocol - skip it
110
            if (! str_contains($proxyIP, $separator)) {
111
                continue;
112
            }
113

114
            // Convert the REMOTE_ADDR IP address to binary, if needed
115
            if (! isset($ip, $sprintf)) {
116
                if ($separator === ':') {
117
                    // Make sure we're having the "full" IPv6 format
118
                    $ip = explode(':', str_replace('::', str_repeat(':', 9 - substr_count($this->ipAddress, ':')), $this->ipAddress));
119

120
                    for ($j = 0; $j < 8; $j++) {
121
                        $ip[$j] = intval($ip[$j], 16);
122
                    }
123

124
                    $sprintf = '%016b%016b%016b%016b%016b%016b%016b%016b';
125
                } else {
126
                    $ip      = explode('.', $this->ipAddress);
127
                    $sprintf = '%08b%08b%08b%08b';
128
                }
129

130
                $ip = vsprintf($sprintf, $ip);
131
            }
132

133
            // Split the netmask length off the network address
134
            sscanf($proxyIP, '%[^/]/%d', $netaddr, $masklen);
135

136
            // Again, an IPv6 address is most likely in a compressed form
137
            if ($separator === ':') {
138
                $netaddr = explode(':', str_replace('::', str_repeat(':', 9 - substr_count($netaddr, ':')), $netaddr));
139

140
                for ($i = 0; $i < 8; $i++) {
141
                    $netaddr[$i] = intval($netaddr[$i], 16);
142
                }
143
            } else {
144
                $netaddr = explode('.', $netaddr);
145
            }
146

147
            // Convert to binary and finally compare
148
            if (strncmp($ip, vsprintf($sprintf, $netaddr), $masklen) === 0) {
149
                $spoof = $this->getClientIP($header);
150

151
                if ($spoof !== null) {
152
                    $this->ipAddress = $spoof;
153
                    break;
154
                }
155
            }
156
        }
157

158
        if (! $ipValidator($this->ipAddress)) {
159
            return $this->ipAddress = '0.0.0.0';
160
        }
161

162
        return $this->ipAddress;
163
    }
164

165
    /**
166
     * Gets the client IP address from the HTTP header.
167
     */
168
    private function getClientIP(string $header): ?string
169
    {
170
        $ipValidator = [
171
            new FormatRules(),
172
            'valid_ip',
173
        ];
174
        $spoof     = null;
175
        $headerObj = $this->header($header);
176

177
        if ($headerObj !== null) {
178
            $spoof = $headerObj->getValue();
179

180
            // Some proxies typically list the whole chain of IP
181
            // addresses through which the client has reached us.
182
            // e.g. client_ip, proxy_ip1, proxy_ip2, etc.
183
            sscanf($spoof, '%[^,]', $spoof);
184

185
            if (! $ipValidator($spoof)) {
186
                $spoof = null;
187
            }
188
        }
189

190
        return $spoof;
191
    }
192

193
    /**
194
     * Fetch an item from the $_SERVER array.
195
     *
196
     * @param array|string|null $index  Index for item to be fetched from $_SERVER
197
     * @param int|null          $filter A filter name to be applied
198
     * @param array|int|null    $flags
199
     *
200
     * @return mixed
201
     */
202
    public function getServer($index = null, $filter = null, $flags = null)
203
    {
204
        return $this->fetchGlobal('server', $index, $filter, $flags);
205
    }
206

207
    /**
208
     * Fetch an item from the $_ENV array.
209
     *
210
     * @param array|string|null $index  Index for item to be fetched from $_ENV
211
     * @param int|null          $filter A filter name to be applied
212
     * @param array|int|null    $flags
213
     *
214
     * @return mixed
215
     *
216
     * @deprecated 4.4.4 This method does not work from the beginning. Use `env()`.
217
     */
218
    public function getEnv($index = null, $filter = null, $flags = null)
219
    {
220
        // @phpstan-ignore-next-line
221
        return $this->fetchGlobal('env', $index, $filter, $flags);
222
    }
223

224
    /**
225
     * Allows manually setting the value of PHP global, like $_GET, $_POST, etc.
226
     *
227
     * @param         string                                   $name  Supergrlobal name (lowercase)
228
     * @phpstan-param 'get'|'post'|'request'|'cookie'|'server' $name
229
     * @param         mixed                                    $value
230
     *
231
     * @return $this
232
     */
233
    public function setGlobal(string $name, $value)
234
    {
235
        $this->globals[$name] = $value;
236

237
        return $this;
238
    }
239

240
    /**
241
     * Fetches one or more items from a global, like cookies, get, post, etc.
242
     * Can optionally filter the input when you retrieve it by passing in
243
     * a filter.
244
     *
245
     * If $type is an array, it must conform to the input allowed by the
246
     * filter_input_array method.
247
     *
248
     * http://php.net/manual/en/filter.filters.sanitize.php
249
     *
250
     * @param         string                                   $name   Supergrlobal name (lowercase)
251
     * @phpstan-param 'get'|'post'|'request'|'cookie'|'server' $name
252
     * @param         array|string|null                        $index
253
     * @param         int|null                                 $filter Filter constant
254
     * @param         array|int|null                           $flags  Options
255
     *
256
     * @return array|bool|float|int|object|string|null
257
     */
258
    public function fetchGlobal(string $name, $index = null, ?int $filter = null, $flags = null)
259
    {
260
        if (! isset($this->globals[$name])) {
261
            $this->populateGlobals($name);
262
        }
263

264
        // Null filters cause null values to return.
265
        $filter ??= FILTER_DEFAULT;
266
        $flags = is_array($flags) ? $flags : (is_numeric($flags) ? (int) $flags : 0);
267

268
        // Return all values when $index is null
269
        if ($index === null) {
270
            $values = [];
271

272
            foreach ($this->globals[$name] as $key => $value) {
273
                $values[$key] = is_array($value)
274
                    ? $this->fetchGlobal($name, $key, $filter, $flags)
275
                    : filter_var($value, $filter, $flags);
276
            }
277

278
            return $values;
279
        }
280

281
        // allow fetching multiple keys at once
282
        if (is_array($index)) {
283
            $output = [];
284

285
            foreach ($index as $key) {
286
                $output[$key] = $this->fetchGlobal($name, $key, $filter, $flags);
287
            }
288

289
            return $output;
290
        }
291

292
        // Does the index contain array notation?
293
        if (($count = preg_match_all('/(?:^[^\[]+)|\[[^]]*\]/', $index, $matches)) > 1) {
294
            $value = $this->globals[$name];
295

296
            for ($i = 0; $i < $count; $i++) {
297
                $key = trim($matches[0][$i], '[]');
298

299
                if ($key === '') { // Empty notation will return the value as array
300
                    break;
301
                }
302

303
                if (isset($value[$key])) {
304
                    $value = $value[$key];
305
                } else {
306
                    return null;
307
                }
308
            }
309
        }
310

311
        if (! isset($value)) {
312
            $value = $this->globals[$name][$index] ?? null;
313
        }
314

315
        if (is_array($value)
316
            && (
317
                $filter !== FILTER_DEFAULT
318
                || (
319
                    (is_numeric($flags) && $flags !== 0)
320
                    || is_array($flags) && $flags !== []
321
                )
322
            )
323
        ) {
324
            // Iterate over array and append filter and flags
325
            array_walk_recursive($value, static function (&$val) use ($filter, $flags): void {
326
                $val = filter_var($val, $filter, $flags);
327
            });
328

329
            return $value;
330
        }
331

332
        // Cannot filter these types of data automatically...
333
        if (is_array($value) || is_object($value) || $value === null) {
334
            return $value;
335
        }
336

337
        return filter_var($value, $filter, $flags);
338
    }
339

340
    /**
341
     * Saves a copy of the current state of one of several PHP globals,
342
     * so we can retrieve them later.
343
     *
344
     * @param         string                                   $name Superglobal name (lowercase)
345
     * @phpstan-param 'get'|'post'|'request'|'cookie'|'server' $name
346
     *
347
     * @return void
348
     */
349
    protected function populateGlobals(string $name)
350
    {
351
        if (! isset($this->globals[$name])) {
352
            $this->globals[$name] = [];
353
        }
354

355
        // Don't populate ENV as it might contain
356
        // sensitive data that we don't want to get logged.
357
        switch ($name) {
358
            case 'get':
359
                $this->globals['get'] = $_GET;
360
                break;
361

362
            case 'post':
363
                $this->globals['post'] = $_POST;
364
                break;
365

366
            case 'request':
367
                $this->globals['request'] = $_REQUEST;
368
                break;
369

370
            case 'cookie':
371
                $this->globals['cookie'] = $_COOKIE;
372
                break;
373

374
            case 'server':
375
                $this->globals['server'] = $_SERVER;
376
                break;
377
        }
378
    }
379
}
380

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.