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

codeigniter4 / CodeIgniter4 / 28847621112

07 Jul 2026 06:55AM UTC coverage: 89.567% (+0.4%) from 89.212%
28847621112

push

github

paulbalandan
Merge remote-tracking branch 'upstream/develop' into 4.8

122 of 125 new or added lines in 12 files covered. (97.6%)

25257 of 28199 relevant lines covered (89.57%)

230.21 hits per line

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

97.39
/system/HTTP/RequestTrait.php
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
    private string $ipAddress = '';
41

42
    /**
43
     * Stores values we've retrieved from PHP globals.
44
     *
45
     * @var array{get?: array, post?: array, request?: array, cookie?: array, server?: array}
46
     *
47
     * @deprecated 4.7.0 Use the Superglobals service instead
48
     */
49
    protected $globals = [];
50

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

64
        $ipValidator = [
100✔
65
            new FormatRules(),
100✔
66
            'valid_ip',
100✔
67
        ];
100✔
68

69
        $proxyIPs = $this->config->proxyIPs;
100✔
70

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

77
        $this->ipAddress = $this->getServer('REMOTE_ADDR') ?? '0.0.0.0';
99✔
78

79
        // If this is a CLI request, $this->ipAddress is '0.0.0.0'.
80
        if ($this->ipAddress === '0.0.0.0') {
99✔
81
            return $this->ipAddress;
71✔
82
        }
83

84
        // @TODO Extract all this IP address logic to another class.
85
        foreach ($proxyIPs as $proxyIP => $header) {
28✔
86
            if ($this->checkIPAgainstProxy($this->ipAddress, (string) $proxyIP)) {
26✔
87
                $spoof = $this->getClientIP($header);
11✔
88

89
                if ($spoof !== null) {
11✔
90
                    $this->ipAddress = $spoof;
8✔
91
                    break;
8✔
92
                }
93
            }
94
        }
95

96
        if (! $ipValidator($this->ipAddress)) {
28✔
97
            return $this->ipAddress = '0.0.0.0';
×
98
        }
99

100
        return $this->ipAddress;
28✔
101
    }
102

103
    /**
104
     * Gets the client IP address from the HTTP header.
105
     */
106
    private function getClientIP(string $header): ?string
107
    {
108
        $ipValidator = [
11✔
109
            new FormatRules(),
11✔
110
            'valid_ip',
11✔
111
        ];
11✔
112
        $spoof     = null;
11✔
113
        $headerObj = $this->header($header);
11✔
114

115
        if ($headerObj !== null) {
11✔
116
            $spoof = $headerObj->getValue();
11✔
117

118
            // Some proxies typically list the whole chain of IP
119
            // addresses through which the client has reached us.
120
            // e.g. client_ip, proxy_ip1, proxy_ip2, etc.
121
            sscanf($spoof, '%[^,]', $spoof);
11✔
122

123
            if (! $ipValidator($spoof)) {
11✔
124
                $spoof = null;
3✔
125
            }
126
        }
127

128
        return $spoof;
11✔
129
    }
130

131
    /**
132
     * Checks if the request comes from one of the trusted proxies
133
     * configured in Config\App::$proxyIPs.
134
     */
135
    protected function isFromTrustedProxy(): bool
136
    {
137
        $proxyIPs = $this->config->proxyIPs;
25✔
138

139
        if (! is_array($proxyIPs) || $proxyIPs === []) {
25✔
140
            return false;
10✔
141
        }
142

143
        $remoteAddr = $this->getServer('REMOTE_ADDR');
15✔
144

145
        if (! is_string($remoteAddr) || $remoteAddr === '') {
15✔
NEW
146
            return false;
×
147
        }
148

149
        foreach (array_keys($proxyIPs) as $proxyIP) {
15✔
150
            if ($this->checkIPAgainstProxy($remoteAddr, (string) $proxyIP)) {
15✔
151
                return true;
6✔
152
            }
153
        }
154

155
        return false;
9✔
156
    }
157

158
    /**
159
     * Checks if the given IP address matches the trusted proxy entry,
160
     * which may be a single IP address or a subnet in CIDR notation.
161
     * Supports both IPv4 and IPv6.
162
     */
163
    private function checkIPAgainstProxy(string $ip, string $proxyIP): bool
164
    {
165
        $maskLength = null;
41✔
166

167
        if (str_contains($proxyIP, '/')) {
41✔
168
            [$proxyIP, $mask] = explode('/', $proxyIP, 2);
28✔
169

170
            if ($mask === '' || ! ctype_digit($mask)) {
28✔
171
                return false;
6✔
172
            }
173

174
            $maskLength = (int) $mask;
22✔
175
        }
176

177
        $binaryIP    = inet_pton($ip);
35✔
178
        $binaryProxy = inet_pton($proxyIP);
35✔
179

180
        if ($binaryIP === false || $binaryProxy === false) {
35✔
181
            return false;
2✔
182
        }
183

184
        // If the proxy entry doesn't match the IP protocol - no match
185
        if (strlen($binaryIP) !== strlen($binaryProxy)) {
33✔
186
            return false;
2✔
187
        }
188

189
        if ($maskLength === null) {
32✔
190
            return $binaryIP === $binaryProxy;
15✔
191
        }
192

193
        if ($maskLength > strlen($binaryIP) * 8) {
21✔
194
            return false;
4✔
195
        }
196

197
        if ($maskLength === 0) {
17✔
NEW
198
            return true;
×
199
        }
200

201
        $fullBytes     = intdiv($maskLength, 8);
17✔
202
        $remainingBits = $maskLength % 8;
17✔
203

204
        if ($fullBytes > 0 && strncmp($binaryIP, $binaryProxy, $fullBytes) !== 0) {
17✔
205
            return false;
9✔
206
        }
207

208
        if ($remainingBits > 0) {
8✔
209
            $bitmask = 0xFF & (0xFF << (8 - $remainingBits));
2✔
210

211
            return (ord($binaryIP[$fullBytes]) & $bitmask) === (ord($binaryProxy[$fullBytes]) & $bitmask);
2✔
212
        }
213

214
        return true;
6✔
215
    }
216

217
    /**
218
     * Fetch an item from the $_SERVER array.
219
     *
220
     * @param array|string|null $index  Index for item to be fetched from $_SERVER
221
     * @param int|null          $filter A filter name to be applied
222
     * @param array|int|null    $flags
223
     *
224
     * @return mixed
225
     */
226
    public function getServer($index = null, $filter = null, $flags = null)
227
    {
228
        return $this->fetchGlobal('server', $index, $filter, $flags);
3,739✔
229
    }
230

231
    /**
232
     * Allows manually setting the value of PHP global, like $_GET, $_POST, etc.
233
     *
234
     * @param 'cookie'|'get'|'post'|'request'|'server' $name  Superglobal name (lowercase)
235
     * @param mixed                                    $value
236
     *
237
     * @return $this
238
     */
239
    public function setGlobal(string $name, $value)
240
    {
241
        // Keep BC with $globals array
242
        $this->globals[$name] = $value;
138✔
243

244
        // Also update Superglobals via service
245
        service('superglobals')->setGlobalArray($name, $value);
138✔
246

247
        return $this;
138✔
248
    }
249

250
    /**
251
     * Fetches one or more items from a global, like cookies, get, post, etc.
252
     * Can optionally filter the input when you retrieve it by passing in
253
     * a filter.
254
     *
255
     * If $type is an array, it must conform to the input allowed by the
256
     * filter_input_array method.
257
     *
258
     * http://php.net/manual/en/filter.filters.sanitize.php
259
     *
260
     * @param 'cookie'|'get'|'post'|'request'|'server' $name   Superglobal name (lowercase)
261
     * @param array|int|string|null                    $index
262
     * @param int|null                                 $filter Filter constant
263
     * @param array|int|null                           $flags  Options
264
     *
265
     * @return mixed
266
     */
267
    public function fetchGlobal(string $name, $index = null, ?int $filter = null, $flags = null)
268
    {
269
        if (! isset($this->globals[$name])) {
3,739✔
270
            $this->populateGlobals($name);
3,693✔
271
        }
272

273
        // Null filters cause null values to return.
274
        $filter ??= FILTER_UNSAFE_RAW;
3,739✔
275
        $flags = is_array($flags) ? $flags : (is_numeric($flags) ? (int) $flags : 0);
3,739✔
276

277
        // Return all values when $index is null
278
        if ($index === null) {
3,739✔
279
            $values = [];
126✔
280

281
            foreach ($this->globals[$name] as $key => $value) {
126✔
282
                $values[$key] = is_array($value)
74✔
283
                    ? $this->fetchGlobal($name, $key, $filter, $flags)
20✔
284
                    : filter_var($value, $filter, $flags);
61✔
285
            }
286

287
            return $values;
126✔
288
        }
289

290
        // allow fetching multiple keys at once
291
        if (is_array($index)) {
3,739✔
292
            $output = [];
8✔
293

294
            foreach ($index as $key) {
8✔
295
                $output[$key] = $this->fetchGlobal($name, $key, $filter, $flags);
8✔
296
            }
297

298
            return $output;
8✔
299
        }
300

301
        // Does the index contain array notation?
302
        if (is_string($index) && ($count = preg_match_all('/(?:^[^\[]+)|\[[^]]*\]/', $index, $matches)) > 1) {
3,739✔
303
            $value = $this->globals[$name];
11✔
304

305
            for ($i = 0; $i < $count; $i++) {
11✔
306
                $key = trim($matches[0][$i], '[]');
11✔
307

308
                if ($key === '') { // Empty notation will return the value as array
11✔
309
                    break;
2✔
310
                }
311

312
                if (isset($value[$key])) {
11✔
313
                    $value = $value[$key];
11✔
314
                } else {
315
                    return null;
2✔
316
                }
317
            }
318
        }
319

320
        if (! isset($value)) {
3,739✔
321
            $value = $this->globals[$name][$index] ?? null;
3,739✔
322
        }
323

324
        if (is_array($value)
3,739✔
325
            && (
326
                $filter !== FILTER_UNSAFE_RAW
3,739✔
327
                || (
3,739✔
328
                    (is_numeric($flags) && $flags !== 0)
3,739✔
329
                    || is_array($flags) && $flags !== []
3,739✔
330
                )
3,739✔
331
            )
332
        ) {
333
            // Iterate over array and append filter and flags
334
            array_walk_recursive($value, static function (&$val) use ($filter, $flags): void {
4✔
335
                $val = filter_var($val, $filter, $flags);
4✔
336
            });
4✔
337

338
            return $value;
4✔
339
        }
340

341
        // Cannot filter these types of data automatically...
342
        if (is_array($value) || is_object($value) || $value === null) {
3,739✔
343
            return $value;
1,706✔
344
        }
345

346
        return filter_var($value, $filter, $flags);
2,358✔
347
    }
348

349
    /**
350
     * Saves a copy of the current state of one of several PHP globals,
351
     * so we can retrieve them later.
352
     *
353
     * @param 'cookie'|'get'|'post'|'request'|'server' $name Superglobal name (lowercase)
354
     *
355
     * @return void
356
     *
357
     * @deprecated 4.7.0 No longer needs to be called explicitly. Used internally to maintain BC with $globals.
358
     */
359
    protected function populateGlobals(string $name)
360
    {
361
        if (! isset($this->globals[$name])) {
362
            $this->globals[$name] = [];
363
        }
364

365
        // Get data from Superglobals service instead of direct access
366
        $this->globals[$name] = service('superglobals')->getGlobalArray($name);
367
    }
368
}
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

© 2026 Coveralls, Inc