This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include
, or for example
via PHP's auto-loading mechanism.
1 | <?php |
||||
2 | /** |
||||
3 | * Geolocation class |
||||
4 | * |
||||
5 | * Handles geolocation of IP Addresses. |
||||
6 | * |
||||
7 | */ |
||||
8 | |||||
9 | defined( 'ABSPATH' ) || exit; |
||||
10 | |||||
11 | /** |
||||
12 | * GetPaid_Geolocation Class. |
||||
13 | */ |
||||
14 | class GetPaid_Geolocation { |
||||
15 | |||||
16 | /** |
||||
17 | * Holds the current user's IP Address. |
||||
18 | * |
||||
19 | * @var string |
||||
20 | */ |
||||
21 | public static $current_user_ip; |
||||
22 | |||||
23 | /** |
||||
24 | * API endpoints for looking up a user IP address. |
||||
25 | * |
||||
26 | * For example, in case a user is on localhost. |
||||
27 | * |
||||
28 | * @var array |
||||
29 | */ |
||||
30 | protected static $ip_lookup_apis = array( |
||||
31 | 'ipify' => 'http://api.ipify.org/', |
||||
32 | 'ipecho' => 'http://ipecho.net/plain', |
||||
33 | 'ident' => 'http://ident.me', |
||||
34 | 'whatismyipaddress' => 'http://bot.whatismyipaddress.com', |
||||
35 | ); |
||||
36 | |||||
37 | /** |
||||
38 | * API endpoints for geolocating an IP address |
||||
39 | * |
||||
40 | * @var array |
||||
41 | */ |
||||
42 | protected static $geoip_apis = array( |
||||
43 | 'ip-api.com' => 'http://ip-api.com/json/%s', |
||||
44 | 'ipinfo.io' => 'https://ipinfo.io/%s/json', |
||||
45 | ); |
||||
46 | |||||
47 | /** |
||||
48 | * Get current user IP Address. |
||||
49 | * |
||||
50 | * @return string |
||||
51 | */ |
||||
52 | public static function get_ip_address() { |
||||
53 | return wpinv_get_ip(); |
||||
54 | } |
||||
55 | |||||
56 | /** |
||||
57 | * Get user IP Address using an external service. |
||||
58 | * This can be used as a fallback for users on localhost where |
||||
59 | * get_ip_address() will be a local IP and non-geolocatable. |
||||
60 | * |
||||
61 | * @return string |
||||
62 | */ |
||||
63 | public static function get_external_ip_address() { |
||||
64 | |||||
65 | $transient_name = 'external_ip_address_0.0.0.0'; |
||||
66 | |||||
67 | if ( '' !== self::get_ip_address() ) { |
||||
68 | $transient_name = 'external_ip_address_' . self::get_ip_address(); |
||||
69 | } |
||||
70 | |||||
71 | // Try retrieving from cache. |
||||
72 | $external_ip_address = get_transient( $transient_name ); |
||||
73 | |||||
74 | if ( false === $external_ip_address ) { |
||||
75 | $external_ip_address = '0.0.0.0'; |
||||
76 | $ip_lookup_services = apply_filters( 'getpaid_geolocation_ip_lookup_apis', self::$ip_lookup_apis ); |
||||
77 | $ip_lookup_services_keys = array_keys( $ip_lookup_services ); |
||||
78 | shuffle( $ip_lookup_services_keys ); |
||||
79 | |||||
80 | foreach ( $ip_lookup_services_keys as $service_name ) { |
||||
81 | $service_endpoint = $ip_lookup_services[ $service_name ]; |
||||
82 | $response = wp_safe_remote_get( $service_endpoint, array( 'timeout' => 2 ) ); |
||||
83 | |||||
84 | if ( ! is_wp_error( $response ) && rest_is_ip_address( $response['body'] ) ) { |
||||
85 | $external_ip_address = apply_filters( 'getpaid_geolocation_ip_lookup_api_response', wpinv_clean( $response['body'] ), $service_name ); |
||||
86 | break; |
||||
87 | } |
||||
88 | } |
||||
89 | |||||
90 | set_transient( $transient_name, $external_ip_address, WEEK_IN_SECONDS ); |
||||
91 | } |
||||
92 | |||||
93 | return $external_ip_address; |
||||
94 | } |
||||
95 | |||||
96 | /** |
||||
97 | * Geolocate an IP address. |
||||
98 | * |
||||
99 | * @param string $ip_address IP Address. |
||||
100 | * @param bool $fallback If true, fallbacks to alternative IP detection (can be slower). |
||||
101 | * @param bool $api_fallback If true, uses geolocation APIs if the database file doesn't exist (can be slower). |
||||
102 | * @return array |
||||
103 | */ |
||||
104 | public static function geolocate_ip( $ip_address = '', $fallback = false, $api_fallback = true ) { |
||||
105 | |||||
106 | if ( empty( $ip_address ) ) { |
||||
107 | $ip_address = self::get_ip_address(); |
||||
108 | } |
||||
109 | |||||
110 | // Update the current user's IP Address. |
||||
111 | self::$current_user_ip = $ip_address; |
||||
112 | |||||
113 | // Filter to allow custom geolocation of the IP address. |
||||
114 | $country_code = apply_filters( 'getpaid_geolocate_ip', false, $ip_address, $fallback, $api_fallback ); |
||||
115 | |||||
116 | if ( false !== $country_code ) { |
||||
117 | |||||
118 | return array( |
||||
119 | 'country' => $country_code, |
||||
120 | 'state' => '', |
||||
121 | 'city' => '', |
||||
122 | 'postcode' => '', |
||||
123 | ); |
||||
124 | |||||
125 | } |
||||
126 | |||||
127 | $country_code = self::get_country_code_from_headers(); |
||||
128 | |||||
129 | /** |
||||
130 | * Get geolocation filter. |
||||
131 | * |
||||
132 | * @since 1.0.19 |
||||
133 | * @param array $geolocation Geolocation data, including country, state, city, and postcode. |
||||
134 | * @param string $ip_address IP Address. |
||||
135 | */ |
||||
136 | $geolocation = apply_filters( |
||||
137 | 'getpaid_get_geolocation', |
||||
138 | array( |
||||
139 | 'country' => $country_code, |
||||
140 | 'state' => '', |
||||
141 | 'city' => '', |
||||
142 | 'postcode' => '', |
||||
143 | ), |
||||
144 | $ip_address |
||||
145 | ); |
||||
146 | |||||
147 | // If we still haven't found a country code, let's consider doing an API lookup. |
||||
148 | if ( '' === $geolocation['country'] && $api_fallback ) { |
||||
149 | $geolocation['country'] = self::geolocate_via_api( $ip_address ); |
||||
150 | } |
||||
151 | |||||
152 | // It's possible that we're in a local environment, in which case the geolocation needs to be done from the |
||||
153 | // external address. |
||||
154 | if ( '' === $geolocation['country'] && $fallback ) { |
||||
155 | $external_ip_address = self::get_external_ip_address(); |
||||
156 | |||||
157 | // Only bother with this if the external IP differs. |
||||
158 | if ( '0.0.0.0' !== $external_ip_address && $external_ip_address !== $ip_address ) { |
||||
159 | return self::geolocate_ip( $external_ip_address, false, $api_fallback ); |
||||
160 | } |
||||
161 | } |
||||
162 | |||||
163 | return array( |
||||
164 | 'country' => $geolocation['country'], |
||||
165 | 'state' => $geolocation['state'], |
||||
166 | 'city' => $geolocation['city'], |
||||
167 | 'postcode' => $geolocation['postcode'], |
||||
168 | ); |
||||
169 | |||||
170 | } |
||||
171 | |||||
172 | /** |
||||
173 | * Fetches the country code from the request headers, if one is available. |
||||
174 | * |
||||
175 | * @since 1.0.19 |
||||
176 | * @return string The country code pulled from the headers, or empty string if one was not found. |
||||
177 | */ |
||||
178 | protected static function get_country_code_from_headers() { |
||||
179 | $country_code = ''; |
||||
180 | |||||
181 | $headers = array( |
||||
182 | 'MM_COUNTRY_CODE', |
||||
183 | 'GEOIP_COUNTRY_CODE', |
||||
184 | 'HTTP_CF_IPCOUNTRY', |
||||
185 | 'HTTP_X_COUNTRY_CODE', |
||||
186 | ); |
||||
187 | |||||
188 | foreach ( $headers as $header ) { |
||||
189 | if ( empty( $_SERVER[ $header ] ) ) { |
||||
190 | continue; |
||||
191 | } |
||||
192 | |||||
193 | $country_code = strtoupper( sanitize_text_field( wp_unslash( $_SERVER[ $header ] ) ) ); |
||||
0 ignored issues
–
show
Bug
introduced
by
![]() |
|||||
194 | break; |
||||
195 | } |
||||
196 | |||||
197 | return $country_code; |
||||
198 | } |
||||
199 | |||||
200 | /** |
||||
201 | * Use APIs to Geolocate the user. |
||||
202 | * |
||||
203 | * Geolocation APIs can be added through the use of the getpaid_geolocation_geoip_apis filter. |
||||
204 | * Provide a name=>value pair for service-slug=>endpoint. |
||||
205 | * |
||||
206 | * If APIs are defined, one will be chosen at random to fulfil the request. After completing, the result |
||||
207 | * will be cached in a transient. |
||||
208 | * |
||||
209 | * @param string $ip_address IP address. |
||||
210 | * @return string |
||||
211 | */ |
||||
212 | protected static function geolocate_via_api( $ip_address ) { |
||||
213 | |||||
214 | // Retrieve from cache... |
||||
215 | $country_code = get_transient( 'geoip_' . $ip_address ); |
||||
216 | |||||
217 | // If missing, retrieve from the API. |
||||
218 | if ( false === $country_code ) { |
||||
219 | $geoip_services = apply_filters( 'getpaid_geolocation_geoip_apis', self::$geoip_apis ); |
||||
220 | |||||
221 | if ( empty( $geoip_services ) ) { |
||||
222 | return ''; |
||||
223 | } |
||||
224 | |||||
225 | $geoip_services_keys = array_keys( $geoip_services ); |
||||
226 | |||||
227 | shuffle( $geoip_services_keys ); |
||||
228 | |||||
229 | foreach ( $geoip_services_keys as $service_name ) { |
||||
230 | |||||
231 | $service_endpoint = $geoip_services[ $service_name ]; |
||||
232 | $response = wp_safe_remote_get( sprintf( $service_endpoint, $ip_address ), array( 'timeout' => 2 ) ); |
||||
233 | $country_code = sanitize_text_field( strtoupper( self::handle_geolocation_response( $response, $service_name ) ) ); |
||||
0 ignored issues
–
show
It seems like
$response can also be of type array ; however, parameter $geolocation_response of GetPaid_Geolocation::handle_geolocation_response() does only seem to accept WP_Error|string , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
234 | |||||
235 | if ( ! empty( $country_code ) ) { |
||||
236 | break; |
||||
237 | } |
||||
238 | } |
||||
239 | |||||
240 | set_transient( 'geoip_' . $ip_address, $country_code, WEEK_IN_SECONDS ); |
||||
241 | } |
||||
242 | |||||
243 | return $country_code; |
||||
244 | } |
||||
245 | |||||
246 | /** |
||||
247 | * Handles geolocation response |
||||
248 | * |
||||
249 | * @param WP_Error|String $geolocation_response |
||||
250 | * @param String $geolocation_service |
||||
251 | * @return string Country code |
||||
252 | */ |
||||
253 | protected static function handle_geolocation_response( $geolocation_response, $geolocation_service ) { |
||||
254 | |||||
255 | if ( is_wp_error( $geolocation_response ) || empty( $geolocation_response['body'] ) ) { |
||||
256 | return ''; |
||||
257 | } |
||||
258 | |||||
259 | if ( $geolocation_service === 'ipinfo.io' ) { |
||||
260 | $data = json_decode( $geolocation_response['body'] ); |
||||
261 | return empty( $data ) || empty( $data->country ) ? '' : $data->country; |
||||
262 | } |
||||
263 | |||||
264 | if ( $geolocation_service === 'ip-api.com' ) { |
||||
265 | $data = json_decode( $geolocation_response['body'] ); |
||||
266 | return empty( $data ) || empty( $data->countryCode ) ? '' : $data->countryCode; |
||||
267 | } |
||||
268 | |||||
269 | return apply_filters( 'getpaid_geolocation_geoip_response_' . $geolocation_service, '', $geolocation_response['body'] ); |
||||
270 | |||||
271 | } |
||||
272 | |||||
273 | } |
||||
274 |