true, 'dns' => true, 'ranges' => true, 'max_ipv4_cidr' => 32, 'usermacros' => false, 'macros' => [] ]; /** * @param array $options */ public function __construct(array $options = []) { foreach (['v6', 'dns', 'ranges', 'max_ipv4_cidr', 'usermacros', 'macros'] as $option) { if (array_key_exists($option, $options)) { $this->options[$option] = $options[$option]; } } $this->ipv4_parser = new CIPv4Parser(); if ($this->options['v6']) { $this->ipv6_parser = new CIPv6Parser(); } if ($this->options['dns']) { $this->dns_parser = new CDnsParser(); } if ($this->options['usermacros']) { $this->user_macro_parser = new CUserMacroParser(); } if ($this->options['macros']) { $this->macro_parser = new CMacroParser(['macros' => $this->options['macros']]); } } /** * Validate comma-separated IP address ranges. * * @param string $ranges * * @return bool */ public function parse($ranges) { $this->error = ''; $this->max_ip_count = '0'; $this->max_ip_range = ''; foreach (explode(',', $ranges) as $range) { $range = trim($range, " \t\r\n"); if (!$this->isValidMask($range) && !$this->isValidRange($range) && !$this->isValidDns($range) && !$this->isValidUserMacro($range) && !$this->isValidMacro($range)) { $this->error = _s('invalid address range "%1$s"', $range); $this->max_ip_count = '0'; $this->max_ip_range = ''; return false; } } return true; } /** * Get first validation error. * * @return string */ public function getError() { return $this->error; } /** * Get maximum number of IP addresses. * * @return string */ public function getMaxIPCount() { return $this->max_ip_count; } /** * Get range with maximum number of IP addresses. * * @return string */ public function getMaxIPRange() { return $this->max_ip_range; } /** * Validate an IP mask. * * @param string $range * * @return bool */ protected function isValidMask($range) { return ($this->isValidMaskIPv4($range) || $this->isValidMaskIPv6($range)); } /** * Validate an IPv4 mask. * * @param string $range * * @return bool */ protected function isValidMaskIPv4($range) { $parts = explode('/', $range); if (count($parts) != 2) { return false; } if ($this->ipv4_parser->parse($parts[0]) != CParser::PARSE_SUCCESS) { return false; } if (!preg_match('/^[0-9]{1,2}$/', $parts[1]) || $parts[1] > $this->options['max_ipv4_cidr']) { return false; } $ip_count = bcpow(2, 32 - $parts[1], 0); if (bccomp($this->max_ip_count, $ip_count) < 0) { $this->max_ip_count = $ip_count; $this->max_ip_range = $range; } return true; } /** * Validate an IPv6 mask. * * @param string $range * * @return bool */ protected function isValidMaskIPv6($range) { if (!$this->options['v6']) { return false; } $parts = explode('/', $range); if (count($parts) != 2) { return false; } if ($this->ipv6_parser->parse($parts[0]) != CParser::PARSE_SUCCESS) { return false; } if (!preg_match('/^[0-9]{1,3}$/', $parts[1]) || $parts[1] > 128) { return false; } $ip_count = bcpow(2, 128 - $parts[1], 0); if (bccomp($this->max_ip_count, $ip_count) < 0) { $this->max_ip_count = $ip_count; $this->max_ip_range = $range; } return true; } /** * Validate an IP address range. * * @param string $range * * @return bool */ protected function isValidRange($range) { return ($this->isValidRangeIPv4($range) || $this->isValidRangeIPv6($range)); } /** * Validate an IPv4 address range. * * @param string $range * * @return bool */ protected function isValidRangeIPv4($range) { $parts = explode('.', $range); $ip_count = '1'; $ip_parts = []; foreach ($parts as $part) { if (preg_match('/^([0-9]{1,3})-([0-9]{1,3})$/', $part, $matches)) { if (!$this->options['ranges'] || $matches[1] > $matches[2]) { return false; } $ip_count = bcmul($ip_count, $matches[2] - $matches[1] + 1, 0); $ip_parts[] = $matches[2]; } else { $ip_parts[] = $part; } } if ($this->ipv4_parser->parse(implode('.', $ip_parts)) != CParser::PARSE_SUCCESS) { return false; } if (bccomp($this->max_ip_count, $ip_count) < 0) { $this->max_ip_count = $ip_count; $this->max_ip_range = $range; } return true; } /** * Validate an IPv6 address range. * * @param string $range * * @return bool */ protected function isValidRangeIPv6($range) { if (!$this->options['v6']) { return false; } $parts = explode(':', $range); $ip_count = '1'; $ip_parts = []; foreach ($parts as $part) { if (preg_match('/^([a-f0-9]{1,4})-([a-f0-9]{1,4})$/i', $part, $matches)) { sscanf($matches[1], '%x', $from); sscanf($matches[2], '%x', $to); if (!$this->options['ranges'] || $from > $to) { return false; } $ip_count = bcmul($ip_count, $to - $from + 1, 0); $ip_parts[] = $matches[1]; } else { $ip_parts[] = $part; } } if ($this->ipv6_parser->parse(implode(':', $ip_parts)) != CParser::PARSE_SUCCESS) { return false; } if (bccomp($this->max_ip_count, $ip_count) < 0) { $this->max_ip_count = $ip_count; $this->max_ip_range = $range; } return true; } /** * Validate a DNS name. * * @param string $range * * @return bool */ protected function isValidDns($range) { if (!$this->options['dns']) { return false; } if ($this->dns_parser->parse($range) != CParser::PARSE_SUCCESS) { return false; } if (bccomp($this->max_ip_count, 1) < 0) { $this->max_ip_count = '1'; $this->max_ip_range = $range; } return true; } /** * Validate a user macros syntax. * * @param string $range * * @return bool */ protected function isValidUserMacro($range) { if (!$this->options['usermacros']) { return false; } return ($this->user_macro_parser->parse($range) == CParser::PARSE_SUCCESS); } /** * Validate a host macros syntax. * Example: {HOST.IP}, {HOST.CONN} etc. * * @param string $range * * @return bool */ protected function isValidMacro($range) { if (!$this->options['macros']) { return false; } return ($this->macro_parser->parse($range) == CParser::PARSE_SUCCESS); } }