• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

codeigniter4 / CodeIgniter4 / 6987273146

25 Nov 2023 05:26AM UTC coverage: 85.263% (-0.005%) from 85.268%
6987273146

push

github

web-flow
Merge pull request #8244 from kenjis/fix-phpdoc-types-for-psalm

docs: fix incorrect PHPDoc types

18583 of 21795 relevant lines covered (85.26%)

198.91 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

95.9
/system/HTTP/RequestTrait.php
1
<?php
2

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

12
namespace CodeIgniter\HTTP;
13

14
use CodeIgniter\Exceptions\ConfigException;
15
use CodeIgniter\Validation\FormatRules;
16
use Config\App;
17

18
/**
19
 * Request Trait
20
 *
21
 * Additional methods to make a PSR-7 Request class
22
 * compliant with the framework's own RequestInterface.
23
 *
24
 * @see https://github.com/php-fig/http-message/blob/master/src/RequestInterface.php
25
 */
26
trait RequestTrait
27
{
28
    /**
29
     * IP address of the current user.
30
     *
31
     * @var string
32
     *
33
     * @deprecated Will become private in a future release
34
     */
35
    protected $ipAddress = '';
36

37
    /**
38
     * Stores values we've retrieved from PHP globals.
39
     *
40
     * @var array{get?: array, post?: array, request?: array, cookie?: array, server?: array}
41
     */
42
    protected $globals = [];
43

44
    /**
45
     * Gets the user's IP address.
46
     *
47
     * @return string IP address if it can be detected.
48
     *                If the IP address is not a valid IP address,
49
     *                then will return '0.0.0.0'.
50
     */
51
    public function getIPAddress(): string
52
    {
53
        if ($this->ipAddress) {
60✔
54
            return $this->ipAddress;
2✔
55
        }
56

57
        $ipValidator = [
60✔
58
            new FormatRules(),
60✔
59
            'valid_ip',
60✔
60
        ];
60✔
61

62
        $proxyIPs = config(App::class)->proxyIPs;
60✔
63

64
        if (! empty($proxyIPs) && (! is_array($proxyIPs) || is_int(array_key_first($proxyIPs)))) {
60✔
65
            throw new ConfigException(
1✔
66
                'You must set an array with Proxy IP address key and HTTP header name value in Config\App::$proxyIPs.'
1✔
67
            );
1✔
68
        }
69

70
        $this->ipAddress = $this->getServer('REMOTE_ADDR');
59✔
71

72
        // If this is a CLI request, $this->ipAddress is null.
73
        if ($this->ipAddress === null) {
59✔
74
            return $this->ipAddress = '0.0.0.0';
41✔
75
        }
76

77
        // @TODO Extract all this IP address logic to another class.
78
        foreach ($proxyIPs as $proxyIP => $header) {
18✔
79
            // Check if we have an IP address or a subnet
80
            if (strpos($proxyIP, '/') === false) {
6✔
81
                // An IP address (and not a subnet) is specified.
82
                // We can compare right away.
83
                if ($proxyIP === $this->ipAddress) {
3✔
84
                    $spoof = $this->getClientIP($header);
3✔
85

86
                    if ($spoof !== null) {
3✔
87
                        $this->ipAddress = $spoof;
3✔
88
                        break;
3✔
89
                    }
90
                }
91

92
                continue;
×
93
            }
94

95
            // We have a subnet ... now the heavy lifting begins
96
            if (! isset($separator)) {
3✔
97
                $separator = $ipValidator($this->ipAddress, 'ipv6') ? ':' : '.';
3✔
98
            }
99

100
            // If the proxy entry doesn't match the IP protocol - skip it
101
            if (strpos($proxyIP, $separator) === false) {
3✔
102
                continue;
×
103
            }
104

105
            // Convert the REMOTE_ADDR IP address to binary, if needed
106
            if (! isset($ip, $sprintf)) {
3✔
107
                if ($separator === ':') {
3✔
108
                    // Make sure we're having the "full" IPv6 format
109
                    $ip = explode(':', str_replace('::', str_repeat(':', 9 - substr_count($this->ipAddress, ':')), $this->ipAddress));
1✔
110

111
                    for ($j = 0; $j < 8; $j++) {
1✔
112
                        $ip[$j] = intval($ip[$j], 16);
1✔
113
                    }
114

115
                    $sprintf = '%016b%016b%016b%016b%016b%016b%016b%016b';
1✔
116
                } else {
117
                    $ip      = explode('.', $this->ipAddress);
2✔
118
                    $sprintf = '%08b%08b%08b%08b';
2✔
119
                }
120

121
                $ip = vsprintf($sprintf, $ip);
3✔
122
            }
123

124
            // Split the netmask length off the network address
125
            sscanf($proxyIP, '%[^/]/%d', $netaddr, $masklen);
3✔
126

127
            // Again, an IPv6 address is most likely in a compressed form
128
            if ($separator === ':') {
3✔
129
                $netaddr = explode(':', str_replace('::', str_repeat(':', 9 - substr_count($netaddr, ':')), $netaddr));
1✔
130

131
                for ($i = 0; $i < 8; $i++) {
1✔
132
                    $netaddr[$i] = intval($netaddr[$i], 16);
1✔
133
                }
134
            } else {
135
                $netaddr = explode('.', $netaddr);
2✔
136
            }
137

138
            // Convert to binary and finally compare
139
            if (strncmp($ip, vsprintf($sprintf, $netaddr), $masklen) === 0) {
3✔
140
                $spoof = $this->getClientIP($header);
3✔
141

142
                if ($spoof !== null) {
3✔
143
                    $this->ipAddress = $spoof;
3✔
144
                    break;
3✔
145
                }
146
            }
147
        }
148

149
        if (! $ipValidator($this->ipAddress)) {
18✔
150
            return $this->ipAddress = '0.0.0.0';
×
151
        }
152

153
        return $this->ipAddress;
18✔
154
    }
155

156
    /**
157
     * Gets the client IP address from the HTTP header.
158
     */
159
    private function getClientIP(string $header): ?string
160
    {
161
        $ipValidator = [
6✔
162
            new FormatRules(),
6✔
163
            'valid_ip',
6✔
164
        ];
6✔
165
        $spoof     = null;
6✔
166
        $headerObj = $this->header($header);
6✔
167

168
        if ($headerObj !== null) {
6✔
169
            $spoof = $headerObj->getValue();
6✔
170

171
            // Some proxies typically list the whole chain of IP
172
            // addresses through which the client has reached us.
173
            // e.g. client_ip, proxy_ip1, proxy_ip2, etc.
174
            sscanf($spoof, '%[^,]', $spoof);
6✔
175

176
            if (! $ipValidator($spoof)) {
6✔
177
                $spoof = null;
×
178
            }
179
        }
180

181
        return $spoof;
6✔
182
    }
183

184
    /**
185
     * Fetch an item from the $_SERVER array.
186
     *
187
     * @param array|string|null $index  Index for item to be fetched from $_SERVER
188
     * @param int|null          $filter A filter name to be applied
189
     * @param array|int|null    $flags
190
     *
191
     * @return mixed
192
     */
193
    public function getServer($index = null, $filter = null, $flags = null)
194
    {
195
        return $this->fetchGlobal('server', $index, $filter, $flags);
1,487✔
196
    }
197

198
    /**
199
     * Fetch an item from the $_ENV array.
200
     *
201
     * @param array|string|null $index  Index for item to be fetched from $_ENV
202
     * @param int|null          $filter A filter name to be applied
203
     * @param array|int|null    $flags
204
     *
205
     * @return mixed
206
     *
207
     * @deprecated 4.4.4 This method does not work from the beginning. Use `env()`.
208
     */
209
    public function getEnv($index = null, $filter = null, $flags = null)
210
    {
211
        // @phpstan-ignore-next-line
212
        return $this->fetchGlobal('env', $index, $filter, $flags);
×
213
    }
214

215
    /**
216
     * Allows manually setting the value of PHP global, like $_GET, $_POST, etc.
217
     *
218
     * @param string $name Supergrlobal name (lowercase)
219
     * @phpstan-param 'get'|'post'|'request'|'cookie'|'server' $name
220
     * @param mixed $value
221
     *
222
     * @return $this
223
     */
224
    public function setGlobal(string $name, $value)
225
    {
226
        $this->globals[$name] = $value;
86✔
227

228
        return $this;
86✔
229
    }
230

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

255
        // Null filters cause null values to return.
256
        $filter ??= FILTER_DEFAULT;
1,525✔
257
        $flags = is_array($flags) ? $flags : (is_numeric($flags) ? (int) $flags : 0);
1,525✔
258

259
        // Return all values when $index is null
260
        if ($index === null) {
1,525✔
261
            $values = [];
76✔
262

263
            foreach ($this->globals[$name] as $key => $value) {
76✔
264
                $values[$key] = is_array($value)
47✔
265
                    ? $this->fetchGlobal($name, $key, $filter, $flags)
12✔
266
                    : filter_var($value, $filter, $flags);
42✔
267
            }
268

269
            return $values;
76✔
270
        }
271

272
        // allow fetching multiple keys at once
273
        if (is_array($index)) {
1,525✔
274
            $output = [];
8✔
275

276
            foreach ($index as $key) {
8✔
277
                $output[$key] = $this->fetchGlobal($name, $key, $filter, $flags);
8✔
278
            }
279

280
            return $output;
8✔
281
        }
282

283
        // Does the index contain array notation?
284
        if (($count = preg_match_all('/(?:^[^\[]+)|\[[^]]*\]/', $index, $matches)) > 1) {
1,525✔
285
            $value = $this->globals[$name];
11✔
286

287
            for ($i = 0; $i < $count; $i++) {
11✔
288
                $key = trim($matches[0][$i], '[]');
11✔
289

290
                if ($key === '') { // Empty notation will return the value as array
11✔
291
                    break;
2✔
292
                }
293

294
                if (isset($value[$key])) {
11✔
295
                    $value = $value[$key];
11✔
296
                } else {
297
                    return null;
2✔
298
                }
299
            }
300
        }
301

302
        if (! isset($value)) {
1,525✔
303
            $value = $this->globals[$name][$index] ?? null;
1,525✔
304
        }
305

306
        if (is_array($value)
1,525✔
307
            && (
308
                $filter !== FILTER_DEFAULT
1,525✔
309
                || (
1,525✔
310
                    (is_numeric($flags) && $flags !== 0)
1,525✔
311
                    || is_array($flags) && $flags !== []
1,525✔
312
                )
1,525✔
313
            )
314
        ) {
315
            // Iterate over array and append filter and flags
316
            array_walk_recursive($value, static function (&$val) use ($filter, $flags) {
4✔
317
                $val = filter_var($val, $filter, $flags);
4✔
318
            });
4✔
319

320
            return $value;
4✔
321
        }
322

323
        // Cannot filter these types of data automatically...
324
        if (is_array($value) || is_object($value) || $value === null) {
1,525✔
325
            return $value;
1,113✔
326
        }
327

328
        return filter_var($value, $filter, $flags);
629✔
329
    }
330

331
    /**
332
     * Saves a copy of the current state of one of several PHP globals,
333
     * so we can retrieve them later.
334
     *
335
     * @param string $name Superglobal name (lowercase)
336
     * @phpstan-param 'get'|'post'|'request'|'cookie'|'server' $name
337
     *
338
     * @return void
339
     */
340
    protected function populateGlobals(string $name)
341
    {
342
        if (! isset($this->globals[$name])) {
1,394✔
343
            $this->globals[$name] = [];
1,394✔
344
        }
345

346
        // Don't populate ENV as it might contain
347
        // sensitive data that we don't want to get logged.
348
        switch ($name) {
349
            case 'get':
1,394✔
350
                $this->globals['get'] = $_GET;
33✔
351
                break;
33✔
352

353
            case 'post':
1,394✔
354
                $this->globals['post'] = $_POST;
83✔
355
                break;
83✔
356

357
            case 'request':
1,394✔
358
                $this->globals['request'] = $_REQUEST;
18✔
359
                break;
18✔
360

361
            case 'cookie':
1,394✔
362
                $this->globals['cookie'] = $_COOKIE;
70✔
363
                break;
70✔
364

365
            case 'server':
1,389✔
366
                $this->globals['server'] = $_SERVER;
1,389✔
367
                break;
1,389✔
368
        }
369
    }
370
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc