Skip to content

Commit 1284e73

Browse files
committed
Merge remote-tracking branch 'upstream/develop' into 4.3
Conflicts: phpstan-baseline.neon.dist system/Session/Handlers/DatabaseHandler.php system/Test/Mock/MockAppConfig.php system/Test/Mock/MockCLIConfig.php user_guide_src/source/installation/upgrading.rst user_guide_src/source/libraries/sessions.rst user_guide_src/source/libraries/sessions/039.php user_guide_src/source/libraries/sessions/040.php user_guide_src/source/libraries/sessions/041.php user_guide_src/source/libraries/sessions/042.php
2 parents 133113d + f04c67d commit 1284e73

21 files changed

Lines changed: 479 additions & 133 deletions

File tree

CHANGELOG.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,41 @@
11
# Changelog
22

3+
## [v4.2.11](https://github.com/codeigniter4/CodeIgniter4/tree/v4.2.10) (2022-12-21)
4+
[Full Changelog](https://github.com/codeigniter4/CodeIgniter4/compare/v4.2.10...v4.2.11)
5+
6+
### Fixed Bugs
7+
* fix: Request::getIPAddress() by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6820
8+
* fix: Model cannot insert when $useAutoIncrement is false by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6827
9+
* fix: View Parser regexp does not support UTF-8 by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6835
10+
* Handle key generation when key is not present in .env by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/6839
11+
* Fix: Controller Test withBody() by @MGatner in https://github.com/codeigniter4/CodeIgniter4/pull/6843
12+
* fix: body assigned via options array in CURLRequest class by @michalsn in https://github.com/codeigniter4/CodeIgniter4/pull/6854
13+
* Fix CreateDatabase leaving altered database config in connection by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/6856
14+
* fix: cast to string all values except arrays in Header class by @michalsn in https://github.com/codeigniter4/CodeIgniter4/pull/6862
15+
* add missing @method Query grouping in Model by @paul45 in https://github.com/codeigniter4/CodeIgniter4/pull/6874
16+
* fix: `composer update` might cause error "Failed to open directory" by @LeMyst in https://github.com/codeigniter4/CodeIgniter4/pull/6833
17+
* fix: required PHP extentions by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6897
18+
* fix: Use Services for the FeatureTestTrait request. by @lonnieezell in https://github.com/codeigniter4/CodeIgniter4/pull/6966
19+
* fix: FileLocator::locateFile() bug with a similar namespace name by @michalsn in https://github.com/codeigniter4/CodeIgniter4/pull/6964
20+
* fix: socket connection in RedisHandler class by @michalsn in https://github.com/codeigniter4/CodeIgniter4/pull/6972
21+
* fix: `spark namespaces` cannot show a namespace with mutilple paths by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6977
22+
* fix: Undefined constant "CodeIgniter\Debug\VENDORPATH" by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6985
23+
* fix: large HTTP input crashes framework by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6984
24+
* fix: empty paths for `rewrite.php` by @datamweb in https://github.com/codeigniter4/CodeIgniter4/pull/6991
25+
* fix: `PHPStan` $cols not defined in `CLI` by @ddevsr in https://github.com/codeigniter4/CodeIgniter4/pull/6994
26+
* Fix MigrationRunnerTest for Windows by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/6855
27+
* fix: turn off `Xdebug` note when running phpstan by @ddevsr in https://github.com/codeigniter4/CodeIgniter4/pull/6851
28+
* Fix ShowTableInfoTest to pass on Windows by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/6853
29+
* Fix MigrateStatusTest for Windows by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/6866
30+
* Fix ShowTableInfoTest when migration records are numerous by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/6868
31+
* Fix CreateDatabaseTest to not leave database by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/6867
32+
* Fix coverage merge warning by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/6885
33+
* fix: replace tabs to spaces by @zl59503020 in https://github.com/codeigniter4/CodeIgniter4/pull/6898
34+
* fix: slack links by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6907
35+
* Fix typo in database/queries.rst by @philFernandez in https://github.com/codeigniter4/CodeIgniter4/pull/6920
36+
* Fix testInsertWithSetAndEscape to make not time dependent by @sclubricants in https://github.com/codeigniter4/CodeIgniter4/pull/6974
37+
* fix: remove unnecessary global variables in rewrite.php by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6973
38+
339
## [v4.2.10](https://github.com/codeigniter4/CodeIgniter4/tree/v4.2.10) (2022-11-05)
440
[Full Changelog](https://github.com/codeigniter4/CodeIgniter4/compare/v4.2.9...v4.2.10)
541

admin/framework/composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
"kint-php/kint": "^5.0.1",
1717
"codeigniter/coding-standard": "^1.5",
1818
"fakerphp/faker": "^1.9",
19-
"friendsofphp/php-cs-fixer": "~3.13.0",
19+
"friendsofphp/php-cs-fixer": "3.13.0",
2020
"mikey179/vfsstream": "^1.6",
2121
"nexusphp/cs-config": "^3.6",
2222
"phpunit/phpunit": "^9.1",

app/Config/App.php

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -331,18 +331,21 @@ class App extends BaseConfig
331331
*
332332
* If your server is behind a reverse proxy, you must whitelist the proxy
333333
* IP addresses from which CodeIgniter should trust headers such as
334-
* HTTP_X_FORWARDED_FOR and HTTP_CLIENT_IP in order to properly identify
334+
* X-Forwarded-For or Client-IP in order to properly identify
335335
* the visitor's IP address.
336336
*
337-
* You can use both an array or a comma-separated list of proxy addresses,
338-
* as well as specifying whole subnets. Here are a few examples:
337+
* You need to set a proxy IP address or IP address with subnets and
338+
* the HTTP header for the client IP address.
339339
*
340-
* Comma-separated: '10.0.1.200,192.168.5.0/24'
341-
* Array: ['10.0.1.200', '192.168.5.0/24']
340+
* Here are some examples:
341+
* [
342+
* '10.0.1.200' => 'X-Forwarded-For',
343+
* '192.168.5.0/24' => 'X-Real-IP',
344+
* ]
342345
*
343-
* @var string|string[]
346+
* @var array<string, string>
344347
*/
345-
public $proxyIPs = '';
348+
public $proxyIPs = [];
346349

347350
/**
348351
* --------------------------------------------------------------------------

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
"kint-php/kint": "^5.0.1",
1717
"codeigniter/coding-standard": "^1.5",
1818
"fakerphp/faker": "^1.9",
19-
"friendsofphp/php-cs-fixer": "~3.13.0",
19+
"friendsofphp/php-cs-fixer": "3.13.0",
2020
"mikey179/vfsstream": "^1.6",
2121
"nexusphp/cs-config": "^3.6",
2222
"nexusphp/tachycardia": "^1.0",

phpstan-baseline.neon.dist

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -175,11 +175,6 @@ parameters:
175175
count: 1
176176
path: system/HTTP/RedirectResponse.php
177177

178-
-
179-
message: "#^Property CodeIgniter\\\\HTTP\\\\Request\\:\\:\\$proxyIPs \\(array\\|string\\) on left side of \\?\\? is not nullable\\.$#"
180-
count: 1
181-
path: system/HTTP/Request.php
182-
183178
-
184179
message: "#^Property CodeIgniter\\\\HTTP\\\\URI\\:\\:\\$fragment \\(string\\) on left side of \\?\\? is not nullable\\.$#"
185180
count: 1

system/CodeIgniter.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ class CodeIgniter
4747
/**
4848
* The current version of CodeIgniter Framework
4949
*/
50-
public const CI_VERSION = '4.2.10';
50+
public const CI_VERSION = '4.2.11';
5151

5252
/**
5353
* App startup time.

system/HTTP/Request.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ class Request extends OutgoingRequest implements RequestInterface
2424
/**
2525
* Proxy IPs
2626
*
27-
* @var array|string
27+
* @var array<string, string>
2828
*
2929
* @deprecated Check the App config directly
3030
*/

system/HTTP/RequestTrait.php

Lines changed: 89 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
namespace CodeIgniter\HTTP;
1313

14+
use CodeIgniter\Exceptions\ConfigException;
1415
use CodeIgniter\Validation\FormatRules;
1516

1617
/**
@@ -43,7 +44,9 @@ trait RequestTrait
4344
/**
4445
* Gets the user's IP address.
4546
*
46-
* @return string IP address
47+
* @return string IP address if it can be detected, or empty string.
48+
* If the IP address is not a valid IP address,
49+
* then will return '0.0.0.0'.
4750
*/
4851
public function getIPAddress(): string
4952
{
@@ -59,93 +62,86 @@ public function getIPAddress(): string
5962
/**
6063
* @deprecated $this->proxyIPs property will be removed in the future
6164
*/
65+
// @phpstan-ignore-next-line
6266
$proxyIPs = $this->proxyIPs ?? config('App')->proxyIPs;
63-
if (! empty($proxyIPs) && ! is_array($proxyIPs)) {
64-
$proxyIPs = explode(',', str_replace(' ', '', $proxyIPs));
67+
if (! empty($proxyIPs)) {
68+
// @phpstan-ignore-next-line
69+
if (! is_array($proxyIPs) || is_int(array_key_first($proxyIPs))) {
70+
throw new ConfigException(
71+
'You must set an array with Proxy IP address key and HTTP header name value in Config\App::$proxyIPs.'
72+
);
73+
}
6574
}
6675

6776
$this->ipAddress = $this->getServer('REMOTE_ADDR');
6877

6978
if ($proxyIPs) {
70-
foreach (['x-forwarded-for', 'client-ip', 'x-client-ip', 'x-cluster-client-ip'] as $header) {
71-
$spoof = null;
72-
$headerObj = $this->header($header);
73-
74-
if ($headerObj !== null) {
75-
$spoof = $headerObj->getValue();
76-
77-
// Some proxies typically list the whole chain of IP
78-
// addresses through which the client has reached us.
79-
// e.g. client_ip, proxy_ip1, proxy_ip2, etc.
80-
sscanf($spoof, '%[^,]', $spoof);
81-
82-
if (! $ipValidator($spoof)) {
83-
$spoof = null;
84-
} else {
85-
break;
86-
}
87-
}
88-
}
89-
90-
if ($spoof) {
91-
foreach ($proxyIPs as $proxyIP) {
92-
// Check if we have an IP address or a subnet
93-
if (strpos($proxyIP, '/') === false) {
94-
// An IP address (and not a subnet) is specified.
95-
// We can compare right away.
96-
if ($proxyIP === $this->ipAddress) {
79+
// @TODO Extract all this IP address logic to another class.
80+
foreach ($proxyIPs as $proxyIP => $header) {
81+
// Check if we have an IP address or a subnet
82+
if (strpos($proxyIP, '/') === false) {
83+
// An IP address (and not a subnet) is specified.
84+
// We can compare right away.
85+
if ($proxyIP === $this->ipAddress) {
86+
$spoof = $this->getClientIP($header);
87+
88+
if ($spoof !== null) {
9789
$this->ipAddress = $spoof;
9890
break;
9991
}
100-
101-
continue;
10292
}
10393

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

109-
// If the proxy entry doesn't match the IP protocol - skip it
110-
if (strpos($proxyIP, $separator) === false) {
111-
continue;
112-
}
97+
// We have a subnet ... now the heavy lifting begins
98+
if (! isset($separator)) {
99+
$separator = $ipValidator($this->ipAddress, 'ipv6') ? ':' : '.';
100+
}
113101

114-
// Convert the REMOTE_ADDR IP address to binary, if needed
115-
if (! isset($ip, $sprintf)) {
116-
if ($separator === ':') {
117-
// Make sure we're have the "full" IPv6 format
118-
$ip = explode(':', str_replace('::', str_repeat(':', 9 - substr_count($this->ipAddress, ':')), $this->ipAddress));
102+
// If the proxy entry doesn't match the IP protocol - skip it
103+
if (strpos($proxyIP, $separator) === false) {
104+
continue;
105+
}
119106

120-
for ($j = 0; $j < 8; $j++) {
121-
$ip[$j] = intval($ip[$j], 16);
122-
}
107+
// Convert the REMOTE_ADDR IP address to binary, if needed
108+
if (! isset($ip, $sprintf)) {
109+
if ($separator === ':') {
110+
// Make sure we're having the "full" IPv6 format
111+
$ip = explode(':', str_replace('::', str_repeat(':', 9 - substr_count($this->ipAddress, ':')), $this->ipAddress));
123112

124-
$sprintf = '%016b%016b%016b%016b%016b%016b%016b%016b';
125-
} else {
126-
$ip = explode('.', $this->ipAddress);
127-
$sprintf = '%08b%08b%08b%08b';
113+
for ($j = 0; $j < 8; $j++) {
114+
$ip[$j] = intval($ip[$j], 16);
128115
}
129116

130-
$ip = vsprintf($sprintf, $ip);
117+
$sprintf = '%016b%016b%016b%016b%016b%016b%016b%016b';
118+
} else {
119+
$ip = explode('.', $this->ipAddress);
120+
$sprintf = '%08b%08b%08b%08b';
131121
}
132122

133-
// Split the netmask length off the network address
134-
sscanf($proxyIP, '%[^/]/%d', $netaddr, $masklen);
123+
$ip = vsprintf($sprintf, $ip);
124+
}
135125

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));
126+
// Split the netmask length off the network address
127+
sscanf($proxyIP, '%[^/]/%d', $netaddr, $masklen);
139128

140-
for ($i = 0; $i < 8; $i++) {
141-
$netaddr[$i] = intval($netaddr[$i], 16);
142-
}
143-
} else {
144-
$netaddr = explode('.', $netaddr);
129+
// Again, an IPv6 address is most likely in a compressed form
130+
if ($separator === ':') {
131+
$netaddr = explode(':', str_replace('::', str_repeat(':', 9 - substr_count($netaddr, ':')), $netaddr));
132+
133+
for ($i = 0; $i < 8; $i++) {
134+
$netaddr[$i] = intval($netaddr[$i], 16);
145135
}
136+
} else {
137+
$netaddr = explode('.', $netaddr);
138+
}
139+
140+
// Convert to binary and finally compare
141+
if (strncmp($ip, vsprintf($sprintf, $netaddr), $masklen) === 0) {
142+
$spoof = $this->getClientIP($header);
146143

147-
// Convert to binary and finally compare
148-
if (strncmp($ip, vsprintf($sprintf, $netaddr), $masklen) === 0) {
144+
if ($spoof !== null) {
149145
$this->ipAddress = $spoof;
150146
break;
151147
}
@@ -160,6 +156,34 @@ public function getIPAddress(): string
160156
return empty($this->ipAddress) ? '' : $this->ipAddress;
161157
}
162158

159+
/**
160+
* Gets the client IP address from the HTTP header.
161+
*/
162+
private function getClientIP(string $header): ?string
163+
{
164+
$ipValidator = [
165+
new FormatRules(),
166+
'valid_ip',
167+
];
168+
$spoof = null;
169+
$headerObj = $this->header($header);
170+
171+
if ($headerObj !== null) {
172+
$spoof = $headerObj->getValue();
173+
174+
// Some proxies typically list the whole chain of IP
175+
// addresses through which the client has reached us.
176+
// e.g. client_ip, proxy_ip1, proxy_ip2, etc.
177+
sscanf($spoof, '%[^,]', $spoof);
178+
179+
if (! $ipValidator($spoof)) {
180+
$spoof = null;
181+
}
182+
}
183+
184+
return $spoof;
185+
}
186+
163187
/**
164188
* Fetch an item from the $_SERVER array.
165189
*

system/Session/Handlers/DatabaseHandler.php

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,11 @@ class DatabaseHandler extends BaseHandler
6161
*/
6262
protected $rowExists = false;
6363

64+
/**
65+
* ID prefix for multiple session cookies
66+
*/
67+
protected string $idPrefix;
68+
6469
/**
6570
* @throws SessionException
6671
*/
@@ -74,9 +79,13 @@ public function __construct(AppConfig $config, string $ipAddress)
7479
// Store Session configurations
7580
if ($session instanceof SessionConfig) {
7681
$this->DBGroup = $session->DBGroup ?? config(Database::class)->defaultGroup;
82+
// Add sessionCookieName for multiple session cookies.
83+
$this->idPrefix = $session->cookieName . ':';
7784
} else {
7885
// `Config/Session.php` is absence
7986
$this->DBGroup = $config->sessionDBGroup ?? config(Database::class)->defaultGroup;
87+
// Add sessionCookieName for multiple session cookies.
88+
$this->idPrefix = $config->sessionCookieName . ':';
8089
}
8190

8291
$this->table = $this->savePath;
@@ -124,7 +133,7 @@ public function read($id)
124133
$this->sessionID = $id;
125134
}
126135

127-
$builder = $this->db->table($this->table)->where('id', $id);
136+
$builder = $this->db->table($this->table)->where('id', $this->idPrefix . $id);
128137

129138
if ($this->matchIP) {
130139
$builder = $builder->where('ip_address', $this->ipAddress);
@@ -191,7 +200,7 @@ public function write($id, $data): bool
191200

192201
if ($this->rowExists === false) {
193202
$insertData = [
194-
'id' => $id,
203+
'id' => $this->idPrefix . $id,
195204
'ip_address' => $this->ipAddress,
196205
'data' => $this->prepareData($data),
197206
];
@@ -206,7 +215,7 @@ public function write($id, $data): bool
206215
return true;
207216
}
208217

209-
$builder = $this->db->table($this->table)->where('id', $id);
218+
$builder = $this->db->table($this->table)->where('id', $this->idPrefix . $id);
210219

211220
if ($this->matchIP) {
212221
$builder = $builder->where('ip_address', $this->ipAddress);
@@ -251,7 +260,7 @@ public function close(): bool
251260
public function destroy($id): bool
252261
{
253262
if ($this->lock) {
254-
$builder = $this->db->table($this->table)->where('id', $id);
263+
$builder = $this->db->table($this->table)->where('id', $this->idPrefix . $id);
255264

256265
if ($this->matchIP) {
257266
$builder = $builder->where('ip_address', $this->ipAddress);
@@ -285,7 +294,11 @@ public function gc($max_lifetime)
285294
$separator = ' ';
286295
$interval = implode($separator, ['', "{$max_lifetime} second", '']);
287296

288-
return $this->db->table($this->table)->where('timestamp <', "now() - INTERVAL {$interval}", false)->delete() ? 1 : $this->fail();
297+
return $this->db->table($this->table)->where(
298+
'timestamp <',
299+
"now() - INTERVAL {$interval}",
300+
false
301+
)->delete() ? 1 : $this->fail();
289302
}
290303

291304
/**

0 commit comments

Comments
 (0)