1<?php 2/** 3 * Zend Framework (http://framework.zend.com/) 4 * 5 * @link http://github.com/zendframework/zf2 for the canonical source repository 6 * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com) 7 * @license http://framework.zend.com/license/new-bsd New BSD License 8 */ 9 10namespace Zend\Validator; 11 12use Traversable; 13 14class Ip extends AbstractValidator 15{ 16 const INVALID = 'ipInvalid'; 17 const NOT_IP_ADDRESS = 'notIpAddress'; 18 19 /** 20 * @var array 21 */ 22 protected $messageTemplates = array( 23 self::INVALID => 'Invalid type given. String expected', 24 self::NOT_IP_ADDRESS => "The input does not appear to be a valid IP address", 25 ); 26 27 /** 28 * Internal options 29 * 30 * @var array 31 */ 32 protected $options = array( 33 'allowipv4' => true, // Enable IPv4 Validation 34 'allowipv6' => true, // Enable IPv6 Validation 35 'allowipvfuture' => false, // Enable IPvFuture Validation 36 'allowliteral' => true, // Enable IPs in literal format (only IPv6 and IPvFuture) 37 ); 38 39 /** 40 * Sets the options for this validator 41 * 42 * @param array|Traversable $options 43 * @throws Exception\InvalidArgumentException If there is any kind of IP allowed or $options is not an array or Traversable. 44 * @return AbstractValidator 45 */ 46 public function setOptions($options = array()) 47 { 48 parent::setOptions($options); 49 50 if (!$this->options['allowipv4'] && !$this->options['allowipv6'] && !$this->options['allowipvfuture']) { 51 throw new Exception\InvalidArgumentException('Nothing to validate. Check your options'); 52 } 53 54 return $this; 55 } 56 57 /** 58 * Returns true if and only if $value is a valid IP address 59 * 60 * @param mixed $value 61 * @return bool 62 */ 63 public function isValid($value) 64 { 65 if (!is_string($value)) { 66 $this->error(self::INVALID); 67 return false; 68 } 69 70 $this->setValue($value); 71 72 if ($this->options['allowipv4'] && $this->validateIPv4($value)) { 73 return true; 74 } else { 75 if ((bool) $this->options['allowliteral']) { 76 static $regex = '/^\[(.*)\]$/'; 77 if ((bool) preg_match($regex, $value, $matches)) { 78 $value = $matches[1]; 79 } 80 } 81 82 if (($this->options['allowipv6'] && $this->validateIPv6($value)) || 83 ($this->options['allowipvfuture'] && $this->validateIPvFuture($value)) 84 ) { 85 return true; 86 } 87 } 88 $this->error(self::NOT_IP_ADDRESS); 89 return false; 90 } 91 92 /** 93 * Validates an IPv4 address 94 * 95 * @param string $value 96 * @return bool 97 */ 98 protected function validateIPv4($value) 99 { 100 if (preg_match('/^([01]{8}.){3}[01]{8}\z/i', $value)) { 101 // binary format 00000000.00000000.00000000.00000000 102 $value = bindec(substr($value, 0, 8)) . '.' . bindec(substr($value, 9, 8)) . '.' 103 . bindec(substr($value, 18, 8)) . '.' . bindec(substr($value, 27, 8)); 104 } elseif (preg_match('/^([0-9]{3}.){3}[0-9]{3}\z/i', $value)) { 105 // octet format 777.777.777.777 106 $value = (int) substr($value, 0, 3) . '.' . (int) substr($value, 4, 3) . '.' 107 . (int) substr($value, 8, 3) . '.' . (int) substr($value, 12, 3); 108 } elseif (preg_match('/^([0-9a-f]{2}.){3}[0-9a-f]{2}\z/i', $value)) { 109 // hex format ff.ff.ff.ff 110 $value = hexdec(substr($value, 0, 2)) . '.' . hexdec(substr($value, 3, 2)) . '.' 111 . hexdec(substr($value, 6, 2)) . '.' . hexdec(substr($value, 9, 2)); 112 } 113 114 $ip2long = ip2long($value); 115 if ($ip2long === false) { 116 return false; 117 } 118 119 return ($value == long2ip($ip2long)); 120 } 121 122 /** 123 * Validates an IPv6 address 124 * 125 * @param string $value Value to check against 126 * @return bool True when $value is a valid ipv6 address 127 * False otherwise 128 */ 129 protected function validateIPv6($value) 130 { 131 if (strlen($value) < 3) { 132 return $value == '::'; 133 } 134 135 if (strpos($value, '.')) { 136 $lastcolon = strrpos($value, ':'); 137 if (!($lastcolon && $this->validateIPv4(substr($value, $lastcolon + 1)))) { 138 return false; 139 } 140 141 $value = substr($value, 0, $lastcolon) . ':0:0'; 142 } 143 144 if (strpos($value, '::') === false) { 145 return preg_match('/\A(?:[a-f0-9]{1,4}:){7}[a-f0-9]{1,4}\z/i', $value); 146 } 147 148 $colonCount = substr_count($value, ':'); 149 if ($colonCount < 8) { 150 return preg_match('/\A(?::|(?:[a-f0-9]{1,4}:)+):(?:(?:[a-f0-9]{1,4}:)*[a-f0-9]{1,4})?\z/i', $value); 151 } 152 153 // special case with ending or starting double colon 154 if ($colonCount == 8) { 155 return preg_match('/\A(?:::)?(?:[a-f0-9]{1,4}:){6}[a-f0-9]{1,4}(?:::)?\z/i', $value); 156 } 157 158 return false; 159 } 160 161 /** 162 * Validates an IPvFuture address. 163 * 164 * IPvFuture is loosely defined in the Section 3.2.2 of RFC 3986 165 * 166 * @param string $value Value to check against 167 * @return bool True when $value is a valid IPvFuture address 168 * False otherwise 169 */ 170 protected function validateIPvFuture($value) 171 { 172 /* 173 * ABNF: 174 * IPvFuture = "v" 1*HEXDIG "." 1*( unreserved / sub-delims / ":" ) 175 * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" 176 * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," 177 * / ";" / "=" 178 */ 179 static $regex = '/^v([[:xdigit:]]+)\.[[:alnum:]\-\._~!\$&\'\(\)\*\+,;=:]+$/'; 180 181 $result = (bool) preg_match($regex, $value, $matches); 182 183 /* 184 * "As such, implementations must not provide the version flag for the 185 * existing IPv4 and IPv6 literal address forms described below." 186 */ 187 return ($result && $matches[1] != 4 && $matches[1] != 6); 188 } 189} 190