View source
<?php
namespace Drupal\field_ipaddress;
class IpAddress {
const IP_FAMILY_4 = 4;
const IP_FAMILY_6 = 6;
const IP_FAMILY_ALL = 10;
const IP_RANGE_SIMPLE = 2;
const IP_RANGE_CIDR = 3;
const IP_RANGE_NONE = 0;
protected $family = NULL;
protected $type = NULL;
protected $start = NULL;
protected $end = NULL;
protected $raw = NULL;
public function family() {
return $this->family;
}
public function type() {
return $this->type;
}
public function start() {
return $this->start;
}
public function end() {
return $this->end;
}
public function __construct($value) {
$this->raw = $value;
$result = $this
->parse($value);
if ($result === FALSE) {
$this->family = NULL;
$this->type = NULL;
$this->start = NULL;
$this->end = NULL;
throw new \Exception('Invalid value.');
}
}
public function inRange($min, $max) {
if (!$this
->isIpAddress($min) || !$this
->isIpAddress($max)) {
throw new \Exception('Invalid value.');
}
if ($this
->getFamily($min) != $this->family || $this
->getFamily($max) != $this->family) {
return FALSE;
}
if ($this->family == self::IP_FAMILY_4) {
return $this
->inRange4($min, $max);
}
else {
return $this
->inRange6($min, $max);
}
}
private function isIpAddress($ip) {
return filter_var($ip, FILTER_VALIDATE_IP);
}
private function isIpV6($ip) {
return filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6);
}
private function isIpV4($ip) {
return filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4);
}
private function getFamily($ip) {
if ($this
->isIpV4($ip)) {
return self::IP_FAMILY_4;
}
return self::IP_FAMILY_6;
}
private function checkSingleDelimiter($value) {
$count = 0;
$delimiters = [
'/',
'*',
'-',
];
foreach ($delimiters as $delimiter) {
if (strpos($value, $delimiter) !== FALSE) {
$count++;
}
}
if ($count > 1) {
throw new \Exception('Cannot combine range delimiters, only one of - / * must be present.');
}
}
private function parse($value) {
$value = trim(str_replace(' ', '', $value));
$this
->checkSingleDelimiter($value);
if (strpos($value, '-') !== FALSE) {
list($start, $end) = explode('-', $value, 2);
if (!$this
->isIpAddress($start)) {
return FALSE;
}
if ($this
->isIpV4($start)) {
$this->family = self::IP_FAMILY_4;
if (strpos($end, '.') === FALSE) {
$end_parts = explode('.', $start);
if (count($end_parts) != 4) {
throw new \Exception('Invalid IP range format.');
}
$end_parts[3] = $end;
$end = implode('.', $end_parts);
if (!$this
->isIpAddress($end)) {
throw new \Exception('Invalid IP range format.');
}
}
else {
if (!$this
->isIpAddress($end)) {
throw new \Exception('Invalid end of range IP.');
}
}
}
else {
$this->family = self::IP_FAMILY_6;
if (!$this
->isIpAddress($end)) {
throw new \Exception('Invalid end of range IP.');
}
}
if ($this
->getFamily($start) !== $this
->getFamily($end)) {
throw new \Exception('Mismatching IP families in range.');
}
$this->start = $start;
$this->end = $end;
$this->type = self::IP_RANGE_SIMPLE;
}
elseif (strpos($value, '*') !== FALSE) {
list($prefix, $suffix) = explode('*', $value, 2);
$start = $prefix . '0' . $suffix;
if (!$this
->isIpAddress($start)) {
throw new \Exception('Invalid IP address.');
}
if ($this
->isIpV4($start)) {
$this->family = self::IP_FAMILY_4;
$end = $prefix . '255' . $suffix;
}
if ($this
->isIpV6($start)) {
$this->family = self::IP_FAMILY_6;
$end = $prefix . 'ff' . $suffix;
}
if (!$this
->isIpAddress($end)) {
throw new \Exception('Invalid IP address.');
}
$this->start = $start;
$this->end = $end;
$this->type = self::IP_RANGE_SIMPLE;
}
elseif (strpos($value, '/') !== FALSE) {
list($ip, $prefix) = explode('/', $value, 2);
if (!$this
->isIpAddress($ip) || !is_numeric($prefix) || $prefix <= 0) {
return FALSE;
}
$this->family = $this
->getFamily($ip);
if ($this->family == self::IP_FAMILY_4 && $prefix > 32 || $this->family == self::IP_FAMILY_6 && $prefix > 128) {
return FALSE;
}
$this->type = self::IP_RANGE_CIDR;
if ($this->family == self::IP_FAMILY_4) {
$this
->calcCidr4($ip, $prefix);
}
else {
$this
->calcCidr6($ip, $prefix);
}
}
elseif ($this
->isIpAddress($value)) {
$this->type = self::IP_RANGE_NONE;
$this->family = $this
->getFamily($value);
$this->start = $value;
$this->end = $value;
}
else {
return FALSE;
}
}
private function calcCidr4($ip, $prefix) {
$this->start = long2ip(ip2long($ip) & -1 << 32 - (int) $prefix);
$this->end = long2ip(ip2long($this->start) + pow(2, 32 - (int) $prefix) - 1);
}
private function calcCidr6($ip, $prefix) {
$start_bin = $this
->packIp6($ip);
$this->start = inet_ntop($start_bin);
$start_hex = reset(unpack('H*', $start_bin));
$flexbits = 128 - $prefix;
$end_hex = $start_hex;
$pos = 31;
while ($flexbits > 0) {
$orig = substr($end_hex, $pos, 1);
$origval = hexdec($orig);
$newval = $origval | pow(2, min(4, $flexbits)) - 1;
$new = dechex($newval);
$end_hex = substr_replace($end_hex, $new, $pos, 1);
$flexbits -= 4;
$pos -= 1;
}
$end_bin = pack('H*', $end_hex);
$this->end = inet_ntop($end_bin);
}
private function packIp4($ip) {
return inet_pton(preg_replace('/\\b0+(?=\\d)/', '', $ip));
}
private function packIp6($ip) {
return inet_pton($ip);
}
private function inRange4($min, $max) {
$min_long = ip2long($min);
$max_long = ip2long($max);
if ($this->type == self::IP_RANGE_NONE) {
$start_long = $end_long = ip2long($this->start);
}
else {
$start_long = ip2long($this->start);
$end_long = ip2long($this->end);
}
return $start_long >= $min_long && $start_long <= $max_long && ($end_long >= $min_long && $end_long <= $max_long);
}
private function inRange6($min, $max) {
$min_bin = inet_pton($min);
$max_bin = inet_pton($max);
if ($this->type == self::IP_RANGE_NONE) {
$start_bin = $end_bin = inet_pton($this->start);
}
else {
$start_bin = inet_pton($this->start);
$end_bin = inet_pton($this->end);
}
return $start_bin >= $min_bin && $start_bin <= $max_bin && ($end_bin >= $min_bin && $end_bin <= $max_bin);
}
}