1111
1212namespace CodeIgniter \HTTP ;
1313
14+ use CodeIgniter \Exceptions \ConfigException ;
1415use 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 *
0 commit comments