public function isValid($value)
{
if (!is_string($value)) {
$this->_error(self::INVALID);
return false;
}
$this->_setValue($value);
// Check input against IP address schema
if (preg_match('/^[0-9a-f:.]*$/i', $value) && $this->_options['ip']->setTranslator($this->getTranslator())->isValid($value)) {
if (!($this->_options['allow'] & self::ALLOW_IP)) {
$this->_error(self::IP_ADDRESS_NOT_ALLOWED);
return false;
} else {
return true;
}
}
// RFC3986 3.2.2 states:
//
// The rightmost domain label of a fully qualified domain name
// in DNS may be followed by a single "." and should be if it is
// necessary to distinguish between the complete domain name and
// some local domain.
//
// (see ZF-6363)
// Local hostnames are allowed to be partitial (ending '.')
if ($this->_options['allow'] & self::ALLOW_LOCAL) {
if (substr($value, -1) === '.') {
$value = substr($value, 0, -1);
if (substr($value, -1) === '.') {
// Empty hostnames (ending '..') are not allowed
$this->_error(self::INVALID_LOCAL_NAME);
return false;
}
}
}
$domainParts = explode('.', $value);
// Prevent partitial IP V4 adresses (ending '.')
if (count($domainParts) == 4 && preg_match('/^[0-9.a-e:.]*$/i', $value) && $this->_options['ip']->setTranslator($this->getTranslator())->isValid($value)) {
$this->_error(self::INVALID_LOCAL_NAME);
}
// Check input against DNS hostname schema
if (count($domainParts) > 1 && strlen($value) >= 4 && strlen($value) <= 254) {
$status = false;
$origenc = iconv_get_encoding('internal_encoding');
iconv_set_encoding('internal_encoding', 'UTF-8');
do {
// First check TLD
$matches = array();
if (preg_match('/([^.]{2,10})$/i', end($domainParts), $matches) || end($domainParts) == 'ایران' || end($domainParts) == '中国' || end($domainParts) == '公司' || end($domainParts) == '网络') {
reset($domainParts);
// Hostname characters are: *(label dot)(label dot label); max 254 chars
// label: id-prefix [*ldh{61} id-prefix]; max 63 chars
// id-prefix: alpha / digit
// ldh: alpha / digit / dash
// Match TLD against known list
$this->_tld = strtolower($matches[1]);
if ($this->_options['tld']) {
if (!in_array($this->_tld, $this->_validTlds)) {
$this->_error(self::UNKNOWN_TLD);
$status = false;
break;
}
}
/**
* Match against IDN hostnames
* Note: Keep label regex short to avoid issues with long patterns when matching IDN hostnames
* @see Zend_Validate_Hostname_Interface
*/
$regexChars = array(0 => '/^[a-z0-9\\x2d]{1,63}$/i');
if ($this->_options['idn'] && isset($this->_validIdns[strtoupper($this->_tld)])) {
if (is_string($this->_validIdns[strtoupper($this->_tld)])) {
$regexChars += (include $this->_validIdns[strtoupper($this->_tld)]);
} else {
$regexChars += $this->_validIdns[strtoupper($this->_tld)];
}
}
// Check each hostname part
$check = 0;
foreach ($domainParts as $domainPart) {
// Decode Punycode domainnames to IDN
if (strpos($domainPart, 'xn--') === 0) {
$domainPart = $this->decodePunycode(substr($domainPart, 4));
if ($domainPart === false) {
return false;
}
}
// Check dash (-) does not start, end or appear in 3rd and 4th positions
if (strpos($domainPart, '-') === 0 || strlen($domainPart) > 2 && strpos($domainPart, '-', 2) == 2 && strpos($domainPart, '-', 3) == 3 || strpos($domainPart, '-') === strlen($domainPart) - 1) {
$this->_error(self::INVALID_DASH);
$status = false;
break 2;
}
// Check each domain part
$checked = false;
foreach ($regexChars as $regexKey => $regexChar) {
$status = @preg_match($regexChar, $domainPart);
if ($status > 0) {
$length = 63;
if (array_key_exists(strtoupper($this->_tld), $this->_idnLength) && array_key_exists($regexKey, $this->_idnLength[strtoupper($this->_tld)])) {
$length = $this->_idnLength[strtoupper($this->_tld)];
}
if (iconv_strlen($domainPart, 'UTF-8') > $length) {
$this->_error(self::INVALID_HOSTNAME);
} else {
$checked = true;
break;
}
}
}
if ($checked) {
++$check;
}
}
// If one of the labels doesn't match, the hostname is invalid
if ($check !== count($domainParts)) {
$this->_error(self::INVALID_HOSTNAME_SCHEMA);
$status = false;
}
} else {
// Hostname not long enough
$this->_error(self::UNDECIPHERABLE_TLD);
$status = false;
}
} while (false);
iconv_set_encoding('internal_encoding', $origenc);
// If the input passes as an Internet domain name, and domain names are allowed, then the hostname
// passes validation
if ($status && $this->_options['allow'] & self::ALLOW_DNS) {
return true;
}
} else {
if ($this->_options['allow'] & self::ALLOW_DNS) {
$this->_error(self::INVALID_HOSTNAME);
}
}
// Check for URI Syntax (RFC3986)
if ($this->_options['allow'] & self::ALLOW_URI) {
if (preg_match("/^([a-zA-Z0-9-._~!\$&\\'()*+,;=]|%[[:xdigit:]]{2}){1,254}\$/i", $value)) {
return true;
} else {
$this->_error(self::INVALID_URI);
}
}
// Check input against local network name schema; last chance to pass validation
$regexLocal = '/^(([a-zA-Z0-9\\x2d]{1,63}\\x2e)*[a-zA-Z0-9\\x2d]{1,63}[\\x2e]{0,1}){1,254}$/';
$status = @preg_match($regexLocal, $value);
// If the input passes as a local network name, and local network names are allowed, then the
// hostname passes validation
$allowLocal = $this->_options['allow'] & self::ALLOW_LOCAL;
if ($status && $allowLocal) {
return true;
}
// If the input does not pass as a local network name, add a message
if (!$status) {
$this->_error(self::INVALID_LOCAL_NAME);
}
// If local network names are not allowed, add a message
if ($status && !$allowLocal) {
$this->_error(self::LOCAL_NAME_NOT_ALLOWED);
}
return false;
}