Commit 451678360fd for woocommerce
commit 451678360fd2014cd1a1bcde4117ad6868309d15
Author: Chi-Hsuan Huang <chihsuan.tw@gmail.com>
Date: Fri Jul 3 19:33:09 2026 +0800
Use HTTPS for all geolocation IP lookup and geo-IP requests (#66223)
* Use HTTPS for all geolocation IP lookup and geo-IP requests
* Add changelog entry for geolocation HTTPS fix
* refactor: retain ip-api.com parsing for BC and scope geolocation test filters
Keep the ip-api.com switch branch so extensions re-adding it via the woocommerce_geolocation_geoip_apis filter still parse correctly, and merge the identical ipinfo.io/country.is cases. Use targeted remove_filter instead of remove_all_filters in the API fallback test to avoid clearing global pre_http_request guards.
diff --git a/plugins/woocommerce/changelog/46099-fix-geolocation-https b/plugins/woocommerce/changelog/46099-fix-geolocation-https
new file mode 100644
index 00000000000..237578697b5
--- /dev/null
+++ b/plugins/woocommerce/changelog/46099-fix-geolocation-https
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Use HTTPS for all geolocation IP-lookup and geo-IP requests. Switch the IP-lookup services (ipify, ipecho, ident, tnedi) to HTTPS and replace the HTTP-only ip-api.com geo-IP provider with the HTTPS-based country.is, so visitor IP addresses are never sent over unencrypted connections.
diff --git a/plugins/woocommerce/includes/class-wc-geolocation.php b/plugins/woocommerce/includes/class-wc-geolocation.php
index 5a0140b317d..e2a86df839d 100644
--- a/plugins/woocommerce/includes/class-wc-geolocation.php
+++ b/plugins/woocommerce/includes/class-wc-geolocation.php
@@ -47,10 +47,10 @@ class WC_Geolocation {
* @var array
*/
private static $ip_lookup_apis = array(
- 'ipify' => 'http://api.ipify.org/',
- 'ipecho' => 'http://ipecho.net/plain',
- 'ident' => 'http://ident.me',
- 'tnedi' => 'http://tnedi.me',
+ 'ipify' => 'https://api.ipify.org/',
+ 'ipecho' => 'https://ipecho.net/plain',
+ 'ident' => 'https://ident.me',
+ 'tnedi' => 'https://tnedi.me',
);
/**
@@ -60,7 +60,7 @@ class WC_Geolocation {
*/
private static $geoip_apis = array(
'ipinfo.io' => 'https://ipinfo.io/%s/json',
- 'ip-api.com' => 'http://ip-api.com/json/%s',
+ 'country.is' => 'https://api.country.is/%s',
);
/**
@@ -310,10 +310,12 @@ class WC_Geolocation {
if ( ! is_wp_error( $response ) && $response['body'] ) {
switch ( $service_name ) {
case 'ipinfo.io':
+ case 'country.is':
$data = json_decode( $response['body'] );
$country_code = isset( $data->country ) ? $data->country : '';
break;
case 'ip-api.com':
+ // Not a default provider (HTTP-only), but retained so extensions that re-add it via the woocommerce_geolocation_geoip_apis filter keep working.
$data = json_decode( $response['body'] );
$country_code = isset( $data->countryCode ) ? $data->countryCode : ''; // @codingStandardsIgnoreLine
break;
diff --git a/plugins/woocommerce/tests/legacy/unit-tests/geolocation/class-wc-test-gelocation.php b/plugins/woocommerce/tests/legacy/unit-tests/geolocation/class-wc-test-gelocation.php
index 18ba769ff65..3336473d2db 100644
--- a/plugins/woocommerce/tests/legacy/unit-tests/geolocation/class-wc-test-gelocation.php
+++ b/plugins/woocommerce/tests/legacy/unit-tests/geolocation/class-wc-test-gelocation.php
@@ -49,4 +49,77 @@ class WC_Tests_Geolocation extends WC_Unit_Test_Case {
$this->assertEquals( '2620:0:ccc::2', WC_Geolocation::get_ip_address() );
unset( $_SERVER['REMOTE_ADDR'] );
}
+
+ /**
+ * Read the value of a private static property on WC_Geolocation.
+ *
+ * @param string $property Property name.
+ * @return mixed
+ */
+ private function get_private_static( $property ) {
+ $ref = new ReflectionProperty( 'WC_Geolocation', $property );
+ $ref->setAccessible( true );
+
+ return $ref->getValue();
+ }
+
+ /**
+ * @testdox Every geo-IP lookup endpoint should use HTTPS so visitor IP addresses are never sent unencrypted.
+ */
+ public function test_geoip_apis_all_use_https() {
+ $apis = $this->get_private_static( 'geoip_apis' );
+
+ foreach ( $apis as $service_name => $endpoint ) {
+ $this->assertStringStartsWith( 'https://', $endpoint, "Geo-IP service {$service_name} must use HTTPS" );
+ }
+
+ $this->assertArrayNotHasKey( 'ip-api.com', $apis, 'The HTTP-only ip-api.com provider should no longer be used' );
+ }
+
+ /**
+ * @testdox Every IP-lookup endpoint should use HTTPS so visitor IP addresses are never sent unencrypted.
+ */
+ public function test_ip_lookup_apis_all_use_https() {
+ $apis = $this->get_private_static( 'ip_lookup_apis' );
+
+ foreach ( $apis as $service_name => $endpoint ) {
+ $this->assertStringStartsWith( 'https://', $endpoint, "IP-lookup service {$service_name} must use HTTPS" );
+ }
+ }
+
+ /**
+ * @testdox Geolocating via the API should parse the country code and only request HTTPS endpoints.
+ */
+ public function test_geolocate_via_api_uses_https_and_parses_country() {
+ $ip_address = '8.8.8.8';
+ $requested_url = '';
+
+ delete_transient( 'geoip_' . $ip_address );
+
+ // Force the database lookup to return nothing so the API fallback runs.
+ $force_empty_geolocation = function ( $data ) {
+ $data['country'] = '';
+ return $data;
+ };
+ add_filter( 'woocommerce_get_geolocation', $force_empty_geolocation, 999 );
+
+ // Intercept the outgoing request; the body is valid for both configured providers.
+ $intercept_request = function ( $pre, $args, $url ) use ( &$requested_url ) {
+ $requested_url = $url;
+ return array(
+ 'body' => wp_json_encode( array( 'country' => 'US' ) ),
+ 'response' => array( 'code' => 200 ),
+ );
+ };
+ add_filter( 'pre_http_request', $intercept_request, 10, 3 );
+
+ $geolocation = WC_Geolocation::geolocate_ip( $ip_address, false, true );
+
+ remove_filter( 'pre_http_request', $intercept_request, 10 );
+ remove_filter( 'woocommerce_get_geolocation', $force_empty_geolocation, 999 );
+ delete_transient( 'geoip_' . $ip_address );
+
+ $this->assertEquals( 'US', $geolocation['country'], 'The country code from the API response should be returned' );
+ $this->assertStringStartsWith( 'https://', $requested_url, 'The geolocation request must be made over HTTPS' );
+ }
}