AyeCode /
invoicing
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
Loading history...
|
|||||
| 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
Loading history...
|
|||||
| 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 |