From 48a37bcc5c0eb890f5886d749c7c6c1c62d7549b Mon Sep 17 00:00:00 2001 From: Ryan Cramer Date: Thu, 12 Aug 2021 12:02:07 -0400 Subject: [PATCH] Fix issue processwire/processwire-issues#957 add IPv6 address support for $session->getIP() method --- wire/core/Session.php | 84 +++++++++++++------ .../FieldtypeComments.module | 26 ++++-- 2 files changed, 76 insertions(+), 34 deletions(-) diff --git a/wire/core/Session.php b/wire/core/Session.php index 7dd006ae..170919b0 100644 --- a/wire/core/Session.php +++ b/wire/core/Session.php @@ -807,23 +807,26 @@ class Session extends Wire implements \IteratorAggregate { } /** - * Get the IP address of the current user (IPv4) - * + * Get the IP address of the current user + * * ~~~~~ * $ip = $session->getIP(); * echo $ip; // outputs 111.222.333.444 * ~~~~~ - * - * @param bool $int Return as a long integer for DB storage? (default=false) + * + * @param bool $int Return as a long integer? (default=false) + * - IPv6 addresses cannot be represented as an integer, so please note that using this int option makes it return a CRC32 + * integer when using IPv6 addresses (3.0.184+). * @param bool|int $useClient Give preference to client headers for IP? HTTP_CLIENT_IP and HTTP_X_FORWARDED_FOR (default=false) - * Specify integer 2 to include potential multiple CSV separated IPs (when provided by client). + * - Specify integer 2 to include potential multiple CSV separated IPs (when provided by client). * @return string|int Returns string by default, or integer if $int argument indicates to. * */ public function getIP($int = false, $useClient = false) { - + $ip = $this->config->sessionForceIP; - + $ipv6 = false; + if(!empty($ip)) { // use IP address specified in $config->sessionForceIP and disregard other options $useClient = false; @@ -831,44 +834,75 @@ class Session extends Wire implements \IteratorAggregate { } else if(empty($_SERVER['REMOTE_ADDR'])) { // when accessing via CLI Interface, $_SERVER['REMOTE_ADDR'] isn't set and trying to get it, throws a php-notice $ip = '127.0.0.1'; - - } else if($useClient) { - if(!empty($_SERVER['HTTP_CLIENT_IP'])) $ip = $_SERVER['HTTP_CLIENT_IP']; - else if(!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) $ip = $_SERVER['HTTP_X_FORWARDED_FOR']; - else if(!empty($_SERVER['REMOTE_ADDR'])) $ip = $_SERVER['REMOTE_ADDR']; - else $ip = '0.0.0.0'; + + } else if($useClient) { + if(!empty($_SERVER['HTTP_CLIENT_IP'])) $ip = $_SERVER['HTTP_CLIENT_IP']; + else if(!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) $ip = $_SERVER['HTTP_X_FORWARDED_FOR']; + else if(!empty($_SERVER['REMOTE_ADDR'])) $ip = $_SERVER['REMOTE_ADDR']; + else $ip = '0.0.0.0'; // It's possible for X_FORWARDED_FOR to have more than one CSV separated IP address, per @tuomassalo if(strpos($ip, ',') !== false && $useClient !== 2) { list($ip) = explode(',', $ip); } // sanitize: if IP contains something other than digits, periods, commas, spaces, // then don't use it and instead fallback to the REMOTE_ADDR. - $test = str_replace(array('.', ',', ' '), '', $ip); - if(!ctype_digit("$test")) $ip = $_SERVER['REMOTE_ADDR']; + $test = str_replace(array('.', ',', ' '), '', $ip); + if(!ctype_digit("$test")) { + if(strpos($test, ':') !== false) { + // ipv6 allowed + $test = filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6); + $ip = $test === false ? $_SERVER['REMOTE_ADDR'] : $test; + } else { + $ip = $_SERVER['REMOTE_ADDR']; + } + } } else { - $ip = $_SERVER['REMOTE_ADDR']; + $ip = $_SERVER['REMOTE_ADDR']; } - + + if(strpos($ip, ':') !== false) { + // attempt to identify an IPv4 version when an integer required for return value + if($int && $ip === '::1') { + $ip = '127.0.0.1'; + } else if($int && strpos($ip, '.') && preg_match('!(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})!', $ip, $m)) { + $ip = $m[1]; // i.e. 0:0:0:0:0:ffff:192.1.56.10 => 192.1.56.10 + } else { + $ipv6 = true; + } + } + if($useClient === 2 && strpos($ip, ',') !== false) { // return multiple IPs - $ips = explode(',', $ip); - foreach($ips as $key => $ip) { - $ip = ip2long(trim($ip)); - if(!$int) $ip = long2ip($ip); - $ips[$key] = $ip; + $ips = array(); + foreach(explode(',', $ip) as $ip) { + if($ipv6) { + $ip = filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6); + if($ip !== false && $int) $ip = crc32($ip); + } else { + $ip = ip2long(trim($ip)); + if(!$int) $ip = long2ip($ip); + } + if($ip !== false) $ips[] = $ip; } $ip = implode(',', $ips); - + + } else if($ipv6) { + $ip = filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6); + if($ip === false) { + $ip = $int ? 0 : '0.0.0.0'; + } else if($int) { + $ip = crc32($ip); + } + } else { // sanitize by converting to and from integer $ip = ip2long(trim($ip)); if(!$int) $ip = long2ip($ip); } - + return $ip; } - /** * Login a user with the given name and password * diff --git a/wire/modules/Fieldtype/FieldtypeComments/FieldtypeComments.module b/wire/modules/Fieldtype/FieldtypeComments/FieldtypeComments.module index 9bde3abf..ff693959 100644 --- a/wire/modules/Fieldtype/FieldtypeComments/FieldtypeComments.module +++ b/wire/modules/Fieldtype/FieldtypeComments/FieldtypeComments.module @@ -399,8 +399,10 @@ class FieldtypeComments extends FieldtypeMulti { * */ public function getDatabaseSchema(Field $field) { + + $database = $this->wire()->database; - $maxIndexLength = $this->wire('database')->getMaxIndexLength(); + $maxIndexLength = $database->getMaxIndexLength(); $websiteSchema = "varchar($maxIndexLength) NOT NULL default ''"; $parentSchema = "int unsigned NOT NULL default 0"; $flagSchema = "int unsigned NOT NULL default 0"; @@ -411,13 +413,13 @@ class FieldtypeComments extends FieldtypeMulti { $upvoteSchema = "int unsigned NOT NULL default 0"; $downvoteSchema = "int unsigned NOT NULL default 0"; $starsSchema = "tinyint unsigned default NULL"; + $ipSchema = "varchar(45) NOT NULL default ''"; $schemaVersion = (int) $field->get('schemaVersion'); $updateSchema = true; if(!$schemaVersion) { // add website field for PW 2.3+ - $database = $this->wire('database'); $table = $database->escapeTable($field->getTable()); try { $database->query("ALTER TABLE `$table` ADD website $websiteSchema"); @@ -429,7 +431,6 @@ class FieldtypeComments extends FieldtypeMulti { if($schemaVersion < 2) { // add parent_id and flags columns - $database = $this->wire('database'); $table = $database->escapeTable($field->getTable()); try { $database->query("ALTER TABLE `$table` ADD parent_id $parentSchema"); @@ -442,7 +443,6 @@ class FieldtypeComments extends FieldtypeMulti { if($schemaVersion < 3) { // add code column (admin code) - $database = $this->wire('database'); $table = $database->escapeTable($field->getTable()); try { $database->query("ALTER TABLE `$table` ADD `code` $codeSchema"); @@ -455,7 +455,6 @@ class FieldtypeComments extends FieldtypeMulti { if($schemaVersion < 4) { // add subcode column (subscriber code) - $database = $this->wire('database'); $table = $database->escapeTable($field->getTable()); try { $database->query("ALTER TABLE `$table` ADD `subcode` $subcodeSchema"); @@ -468,7 +467,6 @@ class FieldtypeComments extends FieldtypeMulti { if($schemaVersion < 5 && $updateSchema) { // add upvote/downvote columns - $database = $this->wire('database'); $table = $database->escapeTable($field->getTable()); $parentSchema = parent::getDatabaseSchema($field); try { @@ -477,7 +475,7 @@ class FieldtypeComments extends FieldtypeMulti { `comment_id` int unsigned NOT NULL, `vote` tinyint NOT NULL, `created` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - `ip` VARCHAR(15) NOT NULL default '', + `ip` $ipSchema, `user_id` int unsigned NOT NULL default 0, PRIMARY KEY (`comment_id`, `ip`, `vote`), INDEX `created` (`created`) @@ -506,7 +504,6 @@ class FieldtypeComments extends FieldtypeMulti { } if($schemaVersion < 6 && $updateSchema) { - $database = $this->wire('database'); $table = $database->escapeTable($field->getTable()); try { $database->query("ALTER TABLE `$table` ADD `stars` $starsSchema"); @@ -520,6 +517,17 @@ class FieldtypeComments extends FieldtypeMulti { } } } + + if($schemaVersion < 7 && $updateSchema) { + $table = $database->escapeTable($field->getTable()); + try { + $database->query("ALTER TABLE `$table` MODIFY `ip` $ipSchema"); + $database->query("ALTER TABLE `{$table}_votes` MODIFY `ip` $ipSchema"); + $schemaVersion = 7; + } catch(\Exception $e) { + $this->error($e->getMessage(), Notice::debug); + } + } $_schemaVersion = (int) $field->get('schemaVersion'); if($_schemaVersion < $schemaVersion) { @@ -538,7 +546,7 @@ class FieldtypeComments extends FieldtypeMulti { $schema['sort'] = "int unsigned NOT NULL"; $schema['created'] = "int unsigned NOT NULL"; $schema['created_users_id'] = "int unsigned NOT NULL"; - $schema['ip'] = "varchar(15) NOT NULL default ''"; + $schema['ip'] = $ipSchema; $schema['user_agent'] = "varchar($maxIndexLength) NOT NULL default ''"; $schemaVersion = $field->get('schemaVersion');