11
11
12
12
namespace CodeIgniter \HTTP ;
13
13
14
+ use CodeIgniter \Exceptions \ConfigException ;
14
15
use CodeIgniter \Validation \FormatRules ;
15
16
16
17
/**
@@ -43,7 +44,9 @@ trait RequestTrait
43
44
/**
44
45
* Gets the user's IP address.
45
46
*
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'.
47
50
*/
48
51
public function getIPAddress (): string
49
52
{
@@ -59,93 +62,86 @@ public function getIPAddress(): string
59
62
/**
60
63
* @deprecated $this->proxyIPs property will be removed in the future
61
64
*/
65
+ // @phpstan-ignore-next-line
62
66
$ 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
+ }
65
74
}
66
75
67
76
$ this ->ipAddress = $ this ->getServer ('REMOTE_ADDR ' );
68
77
69
78
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 ) {
97
89
$ this ->ipAddress = $ spoof ;
98
90
break ;
99
91
}
100
-
101
- continue ;
102
92
}
103
93
104
- // We have a subnet ... now the heavy lifting begins
105
- if (! isset ($ separator )) {
106
- $ separator = $ ipValidator ($ this ->ipAddress , 'ipv6 ' ) ? ': ' : '. ' ;
107
- }
94
+ continue ;
95
+ }
108
96
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
+ }
113
101
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
+ }
119
106
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 ));
123
112
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 );
128
115
}
129
116
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 ' ;
131
121
}
132
122
133
- // Split the netmask length off the network address
134
- sscanf ( $ proxyIP , ' %[^/]/%d ' , $ netaddr , $ masklen );
123
+ $ ip = vsprintf ( $ sprintf , $ ip );
124
+ }
135
125
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 );
139
128
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 );
145
135
}
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 );
146
143
147
- // Convert to binary and finally compare
148
- if (strncmp ($ ip , vsprintf ($ sprintf , $ netaddr ), $ masklen ) === 0 ) {
144
+ if ($ spoof !== null ) {
149
145
$ this ->ipAddress = $ spoof ;
150
146
break ;
151
147
}
@@ -160,6 +156,34 @@ public function getIPAddress(): string
160
156
return empty ($ this ->ipAddress ) ? '' : $ this ->ipAddress ;
161
157
}
162
158
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
+
163
187
/**
164
188
* Fetch an item from the $_SERVER array.
165
189
*
0 commit comments