Added geolocate IPv6 support, closes #8184

This commit is contained in:
Claudio Sanches 2015-05-26 14:41:13 -03:00
parent d53d21781b
commit 44dba5036c
2 changed files with 131 additions and 28 deletions

View File

@ -1395,6 +1395,60 @@ class WC_Geo_IP {
return $this->_common_get_record( $seek_country );
}
function _geoip_seek_country_v6( $ipnum ) {
// arrays from unpack start with offset 1
// yet another php mystery. array_merge work around
// this broken behaviour
$v6vec = array_merge( unpack( 'C16', $ipnum ) );
$offset = 0;
for ( $depth = 127; $depth >= 0; --$depth ) {
if ( $this->flags & self::GEOIP_MEMORY_CACHE ) {
$buf = $this->_safe_substr(
$this->memory_buffer,
2 * $this->record_length * $offset,
2 * $this->record_length
);
} elseif ( $this->flags & self::GEOIP_SHARED_MEMORY ) {
$buf = @shmop_read(
$this->shmid,
2 * $this->record_length * $offset,
2 * $this->record_length
);
} else {
fseek( $this->filehandle, 2 * $this->record_length * $offset, SEEK_SET ) == 0
or trigger_error( 'GeoIP API: fseek failed', E_USER_ERROR );
$buf = fread( $this->filehandle, 2 * $this->record_length );
}
$x = array( 0, 0 );
for ( $i = 0; $i < 2; ++$i ) {
for ( $j = 0; $j < $this->record_length; ++$j ) {
$x[ $i ] += ord( $buf[ $this->record_length * $i + $j ] ) << ( $j * 8 );
}
}
$bnum = 127 - $depth;
$idx = $bnum >> 3;
$b_mask = 1 << ( $bnum & 7 ^ 7 );
if ( ( $v6vec[ $idx ] & $b_mask ) > 0 ) {
if ( $x[1] >= $this->databaseSegments ) {
return $x[1];
}
$offset = $x[1];
} else {
if ( $x[0] >= $this->databaseSegments ) {
return $x[0];
}
$offset = $x[0];
}
}
trigger_error( 'GeoIP API: Error traversing database - perhaps it is corrupt?', E_USER_ERROR );
return false;
}
private function _geoip_seek_country( $ipnum ) {
$offset = 0;
for ( $depth = 31; $depth >= 0; --$depth ) {
@ -1412,7 +1466,7 @@ class WC_Geo_IP {
);
} else {
fseek( $this->filehandle, 2 * $this->record_length * $offset, SEEK_SET ) == 0
or trigger_error( "GeoIP API: fseek failed", E_USER_ERROR );
or trigger_error( 'GeoIP API: fseek failed', E_USER_ERROR );
$buf = fread( $this->filehandle, 2 * $this->record_length );
}
@ -1438,12 +1492,12 @@ class WC_Geo_IP {
}
}
trigger_error( "GeoIP API: Error traversing database - perhaps it is corrupt?", E_USER_ERROR );
trigger_error( 'GeoIP API: Error traversing database - perhaps it is corrupt?', E_USER_ERROR );
return false;
}
function geoip_record_by_addr( $addr ) {
public function geoip_record_by_addr( $addr ) {
if ( $addr == null ) {
return 0;
}
@ -1452,11 +1506,25 @@ class WC_Geo_IP {
return $this->_get_record( $ipnum );
}
function geoip_country_id_by_addr( $addr ) {
public function geoip_country_id_by_addr_v6( $addr ) {
$ipnum = inet_pton( $addr );
return $this->_geoip_seek_country_v6( $ipnum ) - self::GEOIP_COUNTRY_BEGIN;
}
public function geoip_country_id_by_addr( $addr ) {
$ipnum = ip2long( $addr );
return $this->_geoip_seek_country( $ipnum ) - self::GEOIP_COUNTRY_BEGIN;
}
public function geoip_country_code_by_addr_v6( $addr ) {
$country_id = $this->geoip_country_id_by_addr_v6( $addr );
if ( $country_id !== false ) {
return $this->GEOIP_COUNTRY_CODES[ $country_id ];
}
return false;
}
public function geoip_country_code_by_addr( $addr ) {
if ( $this->databaseType == self::GEOIP_CITY_EDITION_REV1 ) {
$record = $this->geoip_record_by_addr( $addr);

View File

@ -9,7 +9,7 @@
* @author WooThemes
* @category Admin
* @package WooCommerce/Classes
* @version 2.3.0
* @version 2.4.0
*/
if ( ! defined( 'ABSPATH' ) ) {
@ -22,7 +22,8 @@ if ( ! defined( 'ABSPATH' ) ) {
class WC_Geolocation {
/** URL to the geolocation database we're using */
const GEOLITE_DB = 'http://geolite.maxmind.com/download/geoip/database/GeoLiteCountry/GeoIP.dat.gz';
const GEOLITE_DB = 'http://geolite.maxmind.com/download/geoip/database/GeoLiteCountry/GeoIP.dat.gz';
const GEOLITE_IPV6_DB = 'http://geolite.maxmind.com/download/geoip/database/GeoIPv6.dat.gz';
/** @var array API endpoints for looking up user IP address */
private static $ip_lookup_apis = array(
@ -119,12 +120,18 @@ class WC_Geolocation {
*/
public static function geolocate_ip( $ip_address = '', $fallback = true ) {
// If GEOIP is enabled in CloudFlare, we can use that (Settings -> CloudFlare Settings -> Settings Overview)
if ( ! empty( $_SERVER[ "HTTP_CF_IPCOUNTRY" ] ) ) {
$country_code = sanitize_text_field( strtoupper( $_SERVER["HTTP_CF_IPCOUNTRY"] ) );
if ( ! empty( $_SERVER['HTTP_CF_IPCOUNTRY'] ) ) {
$country_code = sanitize_text_field( strtoupper( $_SERVER['HTTP_CF_IPCOUNTRY'] ) );
} else {
$ip_address = $ip_address ? $ip_address : self::get_ip_address();
if ( file_exists( self::get_local_database_path() ) ) {
if ( self::is_IPv6( $ip_address ) ) {
$database = self::get_local_database_path( 'v6' );
} else {
$database = self::get_local_database_path();
}
if ( file_exists( $database ) ) {
$country_code = self::geolocate_via_db( $ip_address );
} else {
$country_code = self::geolocate_via_api( $ip_address );
@ -144,11 +151,14 @@ class WC_Geolocation {
/**
* Path to our local db
* @param string $version
* @return string
*/
private static function get_local_database_path() {
private static function get_local_database_path( $version = 'v4' ) {
$version = ( 'v4' == $version ) ? '' : 'v6';
$upload_dir = wp_upload_dir();
return $upload_dir['basedir'] . '/GeoIP.dat';
return $upload_dir['basedir'] . '/GeoIP' . $version . '.dat';
}
/**
@ -164,24 +174,29 @@ class WC_Geolocation {
require_once( ABSPATH . 'wp-admin/includes/file.php' );
$tmp_database = download_url( self::GEOLITE_DB );
$tmp_databases = array(
'v4' => download_url( self::GEOLITE_DB ),
'v6' => download_url( self::GEOLITE_IPV6_DB )
);
if ( ! is_wp_error( $tmp_database ) ) {
$gzhandle = @gzopen( $tmp_database, 'r' );
$handle = @fopen( self::get_local_database_path(), 'w' );
foreach ( $tmp_databases as $tmp_database_version => $tmp_database_path ) {
if ( ! is_wp_error( $tmp_database_path ) ) {
$gzhandle = @gzopen( $tmp_database_path, 'r' );
$handle = @fopen( self::get_local_database_path( $tmp_database_version ), 'w' );
if ( $gzhandle && $handle ) {
while ( ( $string = gzread( $gzhandle, 4096 ) ) != false ) {
fwrite( $handle, $string, strlen( $string ) );
if ( $gzhandle && $handle ) {
while ( ( $string = gzread( $gzhandle, 4096 ) ) != false ) {
fwrite( $handle, $string, strlen( $string ) );
}
gzclose( $gzhandle );
fclose( $handle );
} else {
$logger->add( 'geolocation', 'Unable to open database file' );
}
gzclose( $gzhandle );
fclose( $handle );
@unlink( $tmp_database_path );
} else {
$logger->add( 'geolocation', 'Unable to open database file' );
$logger->add( 'geolocation', 'Unable to download GeoIP Database: ' . $tmp_database_path->get_error_message() );
}
@unlink( $tmp_database );
} else {
$logger->add( 'geolocation', 'Unable to download GeoIP Database: ' . $tmp_database->get_error_message() );
}
}
@ -194,11 +209,19 @@ class WC_Geolocation {
if ( ! class_exists( 'WC_Geo_IP' ) ) {
include_once( 'class-wc-geo-ip.php' );
}
$database = self::get_local_database_path();
$gi = new WC_Geo_IP();
$gi->geoip_open( $database, 0 );
$country_code = $gi->geoip_country_code_by_addr( $ip_address );
$gi = new WC_Geo_IP();
if ( self::is_IPv6( $ip_address ) ) {
$database = self::get_local_database_path( 'v6' );
$gi->geoip_open( $database, 0 );
$country_code = $gi->geoip_country_code_by_addr_v6( $ip_address );
} else {
$database = self::get_local_database_path();
$gi->geoip_open( $database, 0 );
$country_code = $gi->geoip_country_code_by_addr( $ip_address );
}
$gi->geoip_close();
return sanitize_text_field( strtoupper( $country_code ) );
@ -250,6 +273,18 @@ class WC_Geolocation {
return $country_code;
}
/**
* Test if is IPv6
*
* @since 2.4.0
*
* @param string $ip_address
* @return bool
*/
private static function is_IPv6( $ip_address ) {
return ! ( false === strpos( $ip_address, ':' ) );
}
}
WC_Geolocation::init();