PageRenderTime 6ms CodeModel.GetById 514ms app.highlight 100ms RepoModel.GetById 584ms app.codeStats 0ms

/libs/Zend/Validate/Hostname.php

https://github.com/CodeYellowBV/piwik
PHP | 784 lines | 466 code | 72 blank | 246 comment | 82 complexity | cb6fff7c0769efb452f7e04ba185e54a MD5 | raw file
  1<?php
  2/**
  3 * Zend Framework
  4 *
  5 * LICENSE
  6 *
  7 * This source file is subject to the new BSD license that is bundled
  8 * with this package in the file LICENSE.txt.
  9 * It is also available through the world-wide-web at this URL:
 10 * http://framework.zend.com/license/new-bsd
 11 * If you did not receive a copy of the license and are unable to
 12 * obtain it through the world-wide-web, please send an email
 13 * to license@zend.com so we can send you a copy immediately.
 14 *
 15 * @category   Zend
 16 * @package    Zend_Validate
 17 * @copyright  Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
 18 * @license    http://framework.zend.com/license/new-bsd     New BSD License
 19 * @version    $Id: Hostname.php 24307 2011-07-30 02:13:14Z adamlundrigan $
 20 */
 21
 22/**
 23 * @see Zend_Validate_Abstract
 24 */
 25// require_once 'Zend/Validate/Abstract.php';
 26
 27/**
 28 * @see Zend_Validate_Ip
 29 */
 30// require_once 'Zend/Validate/Ip.php';
 31
 32/**
 33 * Please note there are two standalone test scripts for testing IDN characters due to problems
 34 * with file encoding.
 35 *
 36 * The first is tests/Zend/Validate/HostnameTestStandalone.php which is designed to be run on
 37 * the command line.
 38 *
 39 * The second is tests/Zend/Validate/HostnameTestForm.php which is designed to be run via HTML
 40 * to allow users to test entering UTF-8 characters in a form.
 41 *
 42 * @category   Zend
 43 * @package    Zend_Validate
 44 * @copyright  Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
 45 * @license    http://framework.zend.com/license/new-bsd     New BSD License
 46 */
 47class Zend_Validate_Hostname extends Zend_Validate_Abstract
 48{
 49    const CANNOT_DECODE_PUNYCODE  = 'hostnameCannotDecodePunycode';
 50    const INVALID                 = 'hostnameInvalid';
 51    const INVALID_DASH            = 'hostnameDashCharacter';
 52    const INVALID_HOSTNAME        = 'hostnameInvalidHostname';
 53    const INVALID_HOSTNAME_SCHEMA = 'hostnameInvalidHostnameSchema';
 54    const INVALID_LOCAL_NAME      = 'hostnameInvalidLocalName';
 55    const INVALID_URI             = 'hostnameInvalidUri';
 56    const IP_ADDRESS_NOT_ALLOWED  = 'hostnameIpAddressNotAllowed';
 57    const LOCAL_NAME_NOT_ALLOWED  = 'hostnameLocalNameNotAllowed';
 58    const UNDECIPHERABLE_TLD      = 'hostnameUndecipherableTld';
 59    const UNKNOWN_TLD             = 'hostnameUnknownTld';
 60
 61    /**
 62     * @var array
 63     */
 64    protected $_messageTemplates = array(
 65        self::CANNOT_DECODE_PUNYCODE  => "'%value%' appears to be a DNS hostname but the given punycode notation cannot be decoded",
 66        self::INVALID                 => "Invalid type given. String expected",
 67        self::INVALID_DASH            => "'%value%' appears to be a DNS hostname but contains a dash in an invalid position",
 68        self::INVALID_HOSTNAME        => "'%value%' does not match the expected structure for a DNS hostname",
 69        self::INVALID_HOSTNAME_SCHEMA => "'%value%' appears to be a DNS hostname but cannot match against hostname schema for TLD '%tld%'",
 70        self::INVALID_LOCAL_NAME      => "'%value%' does not appear to be a valid local network name",
 71        self::INVALID_URI             => "'%value%' does not appear to be a valid URI hostname",
 72        self::IP_ADDRESS_NOT_ALLOWED  => "'%value%' appears to be an IP address, but IP addresses are not allowed",
 73        self::LOCAL_NAME_NOT_ALLOWED  => "'%value%' appears to be a local network name but local network names are not allowed",
 74        self::UNDECIPHERABLE_TLD      => "'%value%' appears to be a DNS hostname but cannot extract TLD part",
 75        self::UNKNOWN_TLD             => "'%value%' appears to be a DNS hostname but cannot match TLD against known list",
 76    );
 77
 78    /**
 79     * @var array
 80     */
 81    protected $_messageVariables = array(
 82        'tld' => '_tld'
 83    );
 84
 85    /**
 86     * Allows Internet domain names (e.g., example.com)
 87     */
 88    const ALLOW_DNS   = 1;
 89
 90    /**
 91     * Allows IP addresses
 92     */
 93    const ALLOW_IP    = 2;
 94
 95    /**
 96     * Allows local network names (e.g., localhost, www.localdomain)
 97     */
 98    const ALLOW_LOCAL = 4;
 99
100    /**
101     * Allows all types of hostnames
102     */
103    const ALLOW_URI = 8;
104
105    /**
106     * Allows all types of hostnames
107     */
108    const ALLOW_ALL = 15;
109
110    /**
111     * Array of valid top-level-domains
112     *
113     * @see ftp://data.iana.org/TLD/tlds-alpha-by-domain.txt  List of all TLDs by domain
114     * @see http://www.iana.org/domains/root/db/ Official list of supported TLDs
115     * @var array
116     */
117    protected $_validTlds = array(
118        'ac', 'ad', 'ae', 'aero', 'af', 'ag', 'ai', 'al', 'am', 'an', 'ao', 'aq', 'ar', 'arpa',
119        'as', 'asia', 'at', 'au', 'aw', 'ax', 'az', 'ba', 'bb', 'bd', 'be', 'bf', 'bg', 'bh', 'bi',
120        'biz', 'bj', 'bm', 'bn', 'bo', 'br', 'bs', 'bt', 'bv', 'bw', 'by', 'bz', 'ca', 'cat', 'cc',
121        'cd', 'cf', 'cg', 'ch', 'ci', 'ck', 'cl', 'cm', 'cn', 'co', 'com', 'coop', 'cr', 'cu',
122        'cv', 'cx', 'cy', 'cz', 'de', 'dj', 'dk', 'dm', 'do', 'dz', 'ec', 'edu', 'ee', 'eg', 'er',
123        'es', 'et', 'eu', 'fi', 'fj', 'fk', 'fm', 'fo', 'fr', 'ga', 'gb', 'gd', 'ge', 'gf', 'gg',
124        'gh', 'gi', 'gl', 'gm', 'gn', 'gov', 'gp', 'gq', 'gr', 'gs', 'gt', 'gu', 'gw', 'gy', 'hk',
125        'hm', 'hn', 'hr', 'ht', 'hu', 'id', 'ie', 'il', 'im', 'in', 'info', 'int', 'io', 'iq',
126        'ir', 'is', 'it', 'je', 'jm', 'jo', 'jobs', 'jp', 'ke', 'kg', 'kh', 'ki', 'km', 'kn', 'kp',
127        'kr', 'kw', 'ky', 'kz', 'la', 'lb', 'lc', 'li', 'lk', 'lr', 'ls', 'lt', 'lu', 'lv', 'ly',
128        'ma', 'mc', 'md', 'me', 'mg', 'mh', 'mil', 'mk', 'ml', 'mm', 'mn', 'mo', 'mobi', 'mp',
129        'mq', 'mr', 'ms', 'mt', 'mu', 'museum', 'mv', 'mw', 'mx', 'my', 'mz', 'na', 'name', 'nc',
130        'ne', 'net', 'nf', 'ng', 'ni', 'nl', 'no', 'np', 'nr', 'nu', 'nz', 'om', 'org', 'pa', 'pe',
131        'pf', 'pg', 'ph', 'pk', 'pl', 'pm', 'pn', 'pr', 'pro', 'ps', 'pt', 'pw', 'py', 'qa', 're',
132        'ro', 'rs', 'ru', 'rw', 'sa', 'sb', 'sc', 'sd', 'se', 'sg', 'sh', 'si', 'sj', 'sk', 'sl',
133        'sm', 'sn', 'so', 'sr', 'st', 'su', 'sv', 'sy', 'sz', 'tc', 'td', 'tel', 'tf', 'tg', 'th',
134        'tj', 'tk', 'tl', 'tm', 'tn', 'to', 'tp', 'tr', 'travel', 'tt', 'tv', 'tw', 'tz', 'ua',
135        'ug', 'uk', 'um', 'us', 'uy', 'uz', 'va', 'vc', 've', 'vg', 'vi', 'vn', 'vu', 'wf', 'ws',
136        'ye', 'yt', 'yu', 'za', 'zm', 'zw'
137    );
138
139    /**
140     * @var string
141     */
142    protected $_tld;
143
144    /**
145     * Array for valid Idns
146     * @see http://www.iana.org/domains/idn-tables/ Official list of supported IDN Chars
147     * (.AC) Ascension Island http://www.nic.ac/pdf/AC-IDN-Policy.pdf
148     * (.AR) Argentinia http://www.nic.ar/faqidn.html
149     * (.AS) American Samoa http://www.nic.as/idn/chars.cfm
150     * (.AT) Austria http://www.nic.at/en/service/technical_information/idn/charset_converter/
151     * (.BIZ) International http://www.iana.org/domains/idn-tables/
152     * (.BR) Brazil http://registro.br/faq/faq6.html
153     * (.BV) Bouvett Island http://www.norid.no/domeneregistrering/idn/idn_nyetegn.en.html
154     * (.CAT) Catalan http://www.iana.org/domains/idn-tables/tables/cat_ca_1.0.html
155     * (.CH) Switzerland https://nic.switch.ch/reg/ocView.action?res=EF6GW2JBPVTG67DLNIQXU234MN6SC33JNQQGI7L6#anhang1
156     * (.CL) Chile http://www.iana.org/domains/idn-tables/tables/cl_latn_1.0.html
157     * (.COM) International http://www.verisign.com/information-services/naming-services/internationalized-domain-names/index.html
158     * (.DE) Germany http://www.denic.de/en/domains/idns/liste.html
159     * (.DK) Danmark http://www.dk-hostmaster.dk/index.php?id=151
160     * (.ES) Spain https://www.nic.es/media/2008-05/1210147705287.pdf
161     * (.FI) Finland http://www.ficora.fi/en/index/palvelut/fiverkkotunnukset/aakkostenkaytto.html
162     * (.GR) Greece https://grweb.ics.forth.gr/CharacterTable1_en.jsp
163     * (.HU) Hungary http://www.domain.hu/domain/English/szabalyzat/szabalyzat.html
164     * (.INFO) International http://www.nic.info/info/idn
165     * (.IO) British Indian Ocean Territory http://www.nic.io/IO-IDN-Policy.pdf
166     * (.IR) Iran http://www.nic.ir/Allowable_Characters_dot-iran
167     * (.IS) Iceland http://www.isnic.is/domain/rules.php
168     * (.KR) Korea http://www.iana.org/domains/idn-tables/tables/kr_ko-kr_1.0.html
169     * (.LI) Liechtenstein https://nic.switch.ch/reg/ocView.action?res=EF6GW2JBPVTG67DLNIQXU234MN6SC33JNQQGI7L6#anhang1
170     * (.LT) Lithuania http://www.domreg.lt/static/doc/public/idn_symbols-en.pdf
171     * (.MD) Moldova http://www.register.md/
172     * (.MUSEUM) International http://www.iana.org/domains/idn-tables/tables/museum_latn_1.0.html
173     * (.NET) International http://www.verisign.com/information-services/naming-services/internationalized-domain-names/index.html
174     * (.NO) Norway http://www.norid.no/domeneregistrering/idn/idn_nyetegn.en.html
175     * (.NU) Niue http://www.worldnames.net/
176     * (.ORG) International http://www.pir.org/index.php?db=content/FAQs&tbl=FAQs_Registrant&id=2
177     * (.PE) Peru https://www.nic.pe/nuevas_politicas_faq_2.php
178     * (.PL) Poland http://www.dns.pl/IDN/allowed_character_sets.pdf
179     * (.PR) Puerto Rico http://www.nic.pr/idn_rules.asp
180     * (.PT) Portugal https://online.dns.pt/dns_2008/do?com=DS;8216320233;111;+PAGE(4000058)+K-CAT-CODIGO(C.125)+RCNT(100);
181     * (.RU) Russia http://www.iana.org/domains/idn-tables/tables/ru_ru-ru_1.0.html
182     * (.SA) Saudi Arabia http://www.iana.org/domains/idn-tables/tables/sa_ar_1.0.html
183     * (.SE) Sweden http://www.iis.se/english/IDN_campaignsite.shtml?lang=en
184     * (.SH) Saint Helena http://www.nic.sh/SH-IDN-Policy.pdf
185     * (.SJ) Svalbard and Jan Mayen http://www.norid.no/domeneregistrering/idn/idn_nyetegn.en.html
186     * (.TH) Thailand http://www.iana.org/domains/idn-tables/tables/th_th-th_1.0.html
187     * (.TM) Turkmenistan http://www.nic.tm/TM-IDN-Policy.pdf
188     * (.TR) Turkey https://www.nic.tr/index.php
189     * (.VE) Venice http://www.iana.org/domains/idn-tables/tables/ve_es_1.0.html
190     * (.VN) Vietnam http://www.vnnic.vn/english/5-6-300-2-2-04-20071115.htm#1.%20Introduction
191     *
192     * @var array
193     */
194    protected $_validIdns = array(
195        'AC'  => array(1 => '/^[\x{002d}0-9a-zà-öø-ÿāăąćĉċčďđēėęěĝġģĥħīįĵķĺļľŀłńņňŋőœŕŗřśŝşšţťŧūŭůűųŵŷźżž]{1,63}$/iu'),
196        'AR'  => array(1 => '/^[\x{002d}0-9a-zà-ãç-êìíñ-õü]{1,63}$/iu'),
197        'AS'  => array(1 => '/^[\x{002d}0-9a-zà-öø-ÿāăąćĉċčďđēĕėęěĝğġģĥħĩīĭįıĵķĸĺļľłńņňŋōŏőœŕŗřśŝşšţťŧũūŭůűųŵŷźż]{1,63}$/iu'),
198        'AT'  => array(1 => '/^[\x{002d}0-9a-zà-öø-ÿœšž]{1,63}$/iu'),
199        'BIZ' => 'Hostname/Biz.php',
200        'BR'  => array(1 => '/^[\x{002d}0-9a-zà-ãçéíó-õúü]{1,63}$/iu'),
201        'BV'  => array(1 => '/^[\x{002d}0-9a-zàáä-éêñ-ôöøüčđńŋšŧž]{1,63}$/iu'),
202        'CAT' => array(1 => '/^[\x{002d}0-9a-z·àç-éíïòóúü]{1,63}$/iu'),
203        'CH'  => array(1 => '/^[\x{002d}0-9a-zà-öø-ÿœ]{1,63}$/iu'),
204        'CL'  => array(1 => '/^[\x{002d}0-9a-záéíñóúü]{1,63}$/iu'),
205        'CN'  => 'Hostname/Cn.php',
206        'COM' => 'Hostname/Com.php',
207        'DE'  => array(1 => '/^[\x{002d}0-9a-zà-öø-ÿăąāćĉčċďđĕěėęēğĝġģĥħĭĩįīıĵķĺľļłńňņŋŏőōœĸŕřŗśŝšşťţŧŭůűũųūŵŷźžż]{1,63}$/iu'),
208        'DK'  => array(1 => '/^[\x{002d}0-9a-zäéöü]{1,63}$/iu'),
209        'ES'  => array(1 => '/^[\x{002d}0-9a-zàáçèéíïñòóúü·]{1,63}$/iu'),
210        'EU'  => array(1 => '/^[\x{002d}0-9a-zà-öø-ÿ]{1,63}$/iu',
211            2 => '/^[\x{002d}0-9a-zāăąćĉċčďđēĕėęěĝğġģĥħĩīĭįıĵķĺļľŀłńņňʼnŋōŏőœŕŗřśŝšťŧũūŭůűųŵŷźżž]{1,63}$/iu',
212            3 => '/^[\x{002d}0-9a-zșț]{1,63}$/iu',
213            4 => '/^[\x{002d}0-9a-zΐάέήίΰαβγδεζηθικλμνξοπρςστυφχψωϊϋόύώ]{1,63}$/iu',
214            5 => '/^[\x{002d}0-9a-zабвгдежзийклмнопрстуфхцчшщъыьэюя]{1,63}$/iu',
215            6 => '/^[\x{002d}0-9a-zἀ-ἇἐ-ἕἠ-ἧἰ-ἷὀ-ὅὐ-ὗὠ-ὧὰ-ώᾀ-ᾇᾐ-ᾗᾠ-ᾧᾰ-ᾴᾶᾷῂῃῄῆῇῐ-ΐῖῗῠ-ῧῲῳῴῶῷ]{1,63}$/iu'),
216        'FI'  => array(1 => '/^[\x{002d}0-9a-zäåö]{1,63}$/iu'),
217        'GR'  => array(1 => '/^[\x{002d}0-9a-zΆΈΉΊΌΎ-ΡΣ-ώἀ-ἕἘ-Ἕἠ-ὅὈ-Ὅὐ-ὗὙὛὝὟ-ώᾀ-ᾴᾶ-ᾼῂῃῄῆ-ῌῐ-ΐῖ-Ίῠ-Ῥῲῳῴῶ-ῼ]{1,63}$/iu'),
218        'HK'  => 'Hostname/Cn.php',
219        'HU'  => array(1 => '/^[\x{002d}0-9a-záéíóöúüőű]{1,63}$/iu'),
220        'INFO'=> array(1 => '/^[\x{002d}0-9a-zäåæéöøü]{1,63}$/iu',
221            2 => '/^[\x{002d}0-9a-záéíóöúüőű]{1,63}$/iu',
222            3 => '/^[\x{002d}0-9a-záæéíðóöúýþ]{1,63}$/iu',
223            4 => '/^[\x{AC00}-\x{D7A3}]{1,17}$/iu',
224            5 => '/^[\x{002d}0-9a-zāčēģīķļņōŗšūž]{1,63}$/iu',
225            6 => '/^[\x{002d}0-9a-ząčėęįšūųž]{1,63}$/iu',
226            7 => '/^[\x{002d}0-9a-zóąćęłńśźż]{1,63}$/iu',
227            8 => '/^[\x{002d}0-9a-záéíñóúü]{1,63}$/iu'),
228        'IO'  => array(1 => '/^[\x{002d}0-9a-zà-öø-ÿăąāćĉčċďđĕěėęēğĝġģĥħĭĩįīıĵķĺľļłńňņŋŏőōœĸŕřŗśŝšşťţŧŭůűũųūŵŷźžż]{1,63}$/iu'),
229        'IS'  => array(1 => '/^[\x{002d}0-9a-záéýúíóþæöð]{1,63}$/iu'),
230        'JP'  => 'Hostname/Jp.php',
231        'KR'  => array(1 => '/^[\x{AC00}-\x{D7A3}]{1,17}$/iu'),
232        'LI'  => array(1 => '/^[\x{002d}0-9a-zà-öø-ÿœ]{1,63}$/iu'),
233        'LT'  => array(1 => '/^[\x{002d}0-9ąčęėįšųūž]{1,63}$/iu'),
234        'MD'  => array(1 => '/^[\x{002d}0-9ăâîşţ]{1,63}$/iu'),
235        'MUSEUM' => array(1 => '/^[\x{002d}0-9a-zà-öø-ÿāăąćċčďđēėęěğġģħīįıķĺļľłńņňŋōőœŕŗřśşšţťŧūůűųŵŷźżžǎǐǒǔ\x{01E5}\x{01E7}\x{01E9}\x{01EF}ə\x{0292}ẁẃẅỳ]{1,63}$/iu'),
236        'NET' => 'Hostname/Com.php',
237        'NO'  => array(1 => '/^[\x{002d}0-9a-zàáä-éêñ-ôöøüčđńŋšŧž]{1,63}$/iu'),
238        'NU'  => 'Hostname/Com.php',
239        'ORG' => array(1 => '/^[\x{002d}0-9a-záéíñóúü]{1,63}$/iu',
240            2 => '/^[\x{002d}0-9a-zóąćęłńśźż]{1,63}$/iu',
241            3 => '/^[\x{002d}0-9a-záäåæéëíðóöøúüýþ]{1,63}$/iu',
242            4 => '/^[\x{002d}0-9a-záéíóöúüőű]{1,63}$/iu',
243            5 => '/^[\x{002d}0-9a-ząčėęįšūųž]{1,63}$/iu',
244            6 => '/^[\x{AC00}-\x{D7A3}]{1,17}$/iu',
245            7 => '/^[\x{002d}0-9a-zāčēģīķļņōŗšūž]{1,63}$/iu'),
246        'PE'  => array(1 => '/^[\x{002d}0-9a-zñáéíóúü]{1,63}$/iu'),
247        'PL'  => array(1 => '/^[\x{002d}0-9a-zāčēģīķļņōŗšūž]{1,63}$/iu',
248            2 => '/^[\x{002d}а-ик-ш\x{0450}ѓѕјљњќџ]{1,63}$/iu',
249            3 => '/^[\x{002d}0-9a-zâîăşţ]{1,63}$/iu',
250            4 => '/^[\x{002d}0-9а-яё\x{04C2}]{1,63}$/iu',
251            5 => '/^[\x{002d}0-9a-zàáâèéêìíîòóôùúûċġħż]{1,63}$/iu',
252            6 => '/^[\x{002d}0-9a-zàäåæéêòóôöøü]{1,63}$/iu',
253            7 => '/^[\x{002d}0-9a-zóąćęłńśźż]{1,63}$/iu',
254            8 => '/^[\x{002d}0-9a-zàáâãçéêíòóôõúü]{1,63}$/iu',
255            9 => '/^[\x{002d}0-9a-zâîăşţ]{1,63}$/iu',
256            10=> '/^[\x{002d}0-9a-záäéíóôúýčďĺľňŕšťž]{1,63}$/iu',
257            11=> '/^[\x{002d}0-9a-zçë]{1,63}$/iu',
258            12=> '/^[\x{002d}0-9а-ик-шђјљњћџ]{1,63}$/iu',
259            13=> '/^[\x{002d}0-9a-zćčđšž]{1,63}$/iu',
260            14=> '/^[\x{002d}0-9a-zâçöûüğış]{1,63}$/iu',
261            15=> '/^[\x{002d}0-9a-záéíñóúü]{1,63}$/iu',
262            16=> '/^[\x{002d}0-9a-zäõöüšž]{1,63}$/iu',
263            17=> '/^[\x{002d}0-9a-zĉĝĥĵŝŭ]{1,63}$/iu',
264            18=> '/^[\x{002d}0-9a-zâäéëîô]{1,63}$/iu',
265            19=> '/^[\x{002d}0-9a-zàáâäåæçèéêëìíîïðñòôöøùúûüýćčłńřśš]{1,63}$/iu',
266            20=> '/^[\x{002d}0-9a-zäåæõöøüšž]{1,63}$/iu',
267            21=> '/^[\x{002d}0-9a-zàáçèéìíòóùú]{1,63}$/iu',
268            22=> '/^[\x{002d}0-9a-zàáéíóöúüőű]{1,63}$/iu',
269            23=> '/^[\x{002d}0-9ΐά-ώ]{1,63}$/iu',
270            24=> '/^[\x{002d}0-9a-zàáâåæçèéêëðóôöøüþœ]{1,63}$/iu',
271            25=> '/^[\x{002d}0-9a-záäéíóöúüýčďěňřšťůž]{1,63}$/iu',
272            26=> '/^[\x{002d}0-9a-z·àçèéíïòóúü]{1,63}$/iu',
273            27=> '/^[\x{002d}0-9а-ъьюя\x{0450}\x{045D}]{1,63}$/iu',
274            28=> '/^[\x{002d}0-9а-яёіў]{1,63}$/iu',
275            29=> '/^[\x{002d}0-9a-ząčėęįšūųž]{1,63}$/iu',
276            30=> '/^[\x{002d}0-9a-záäåæéëíðóöøúüýþ]{1,63}$/iu',
277            31=> '/^[\x{002d}0-9a-zàâæçèéêëîïñôùûüÿœ]{1,63}$/iu',
278            32=> '/^[\x{002d}0-9а-щъыьэюяёєіїґ]{1,63}$/iu',
279            33=> '/^[\x{002d}0-9א-ת]{1,63}$/iu'),
280        'PR'  => array(1 => '/^[\x{002d}0-9a-záéíóúñäëïüöâêîôûàèùæçœãõ]{1,63}$/iu'),
281        'PT'  => array(1 => '/^[\x{002d}0-9a-záàâãçéêíóôõú]{1,63}$/iu'),
282        'RU'  => array(1 => '/^[\x{002d}0-9а-яё]{1,63}$/iu'),
283        'SA'  => array(1 => '/^[\x{002d}.0-9\x{0621}-\x{063A}\x{0641}-\x{064A}\x{0660}-\x{0669}]{1,63}$/iu'),
284        'SE'  => array(1 => '/^[\x{002d}0-9a-zäåéöü]{1,63}$/iu'),
285        'SH'  => array(1 => '/^[\x{002d}0-9a-zà-öø-ÿăąāćĉčċďđĕěėęēğĝġģĥħĭĩįīıĵķĺľļłńňņŋŏőōœĸŕřŗśŝšşťţŧŭůűũųūŵŷźžż]{1,63}$/iu'),
286        'SJ'  => array(1 => '/^[\x{002d}0-9a-zàáä-éêñ-ôöøüčđńŋšŧž]{1,63}$/iu'),
287        'TH'  => array(1 => '/^[\x{002d}0-9a-z\x{0E01}-\x{0E3A}\x{0E40}-\x{0E4D}\x{0E50}-\x{0E59}]{1,63}$/iu'),
288        'TM'  => array(1 => '/^[\x{002d}0-9a-zà-öø-ÿāăąćĉċčďđēėęěĝġģĥħīįĵķĺļľŀłńņňŋőœŕŗřśŝşšţťŧūŭůűųŵŷźżž]{1,63}$/iu'),
289        'TW'  => 'Hostname/Cn.php',
290        'TR'  => array(1 => '/^[\x{002d}0-9a-zğıüşöç]{1,63}$/iu'),
291        'VE'  => array(1 => '/^[\x{002d}0-9a-záéíóúüñ]{1,63}$/iu'),
292        'VN'  => array(1 => '/^[ÀÁÂÃÈÉÊÌÍÒÓÔÕÙÚÝàáâãèéêìíòóôõùúýĂăĐđĨĩŨũƠơƯư\x{1EA0}-\x{1EF9}]{1,63}$/iu'),
293        'ایران' => array(1 => '/^[\x{0621}-\x{0624}\x{0626}-\x{063A}\x{0641}\x{0642}\x{0644}-\x{0648}\x{067E}\x{0686}\x{0698}\x{06A9}\x{06AF}\x{06CC}\x{06F0}-\x{06F9}]{1,30}$/iu'),
294        '中国' => 'Hostname/Cn.php',
295        '公司' => 'Hostname/Cn.php',
296        '网络' => 'Hostname/Cn.php'
297    );
298
299    protected $_idnLength = array(
300        'BIZ' => array(5 => 17, 11 => 15, 12 => 20),
301        'CN'  => array(1 => 20),
302        'COM' => array(3 => 17, 5 => 20),
303        'HK'  => array(1 => 15),
304        'INFO'=> array(4 => 17),
305        'KR'  => array(1 => 17),
306        'NET' => array(3 => 17, 5 => 20),
307        'ORG' => array(6 => 17),
308        'TW'  => array(1 => 20),
309        'ایران' => array(1 => 30),
310        '中国' => array(1 => 20),
311        '公司' => array(1 => 20),
312        '网络' => array(1 => 20),
313    );
314
315    protected $_options = array(
316        'allow' => self::ALLOW_DNS,
317        'idn'   => true,
318        'tld'   => true,
319        'ip'    => null
320    );
321
322    /**
323     * Sets validator options
324     *
325     * @param integer          $allow       OPTIONAL Set what types of hostname to allow (default ALLOW_DNS)
326     * @param boolean          $validateIdn OPTIONAL Set whether IDN domains are validated (default true)
327     * @param boolean          $validateTld OPTIONAL Set whether the TLD element of a hostname is validated (default true)
328     * @param Zend_Validate_Ip $ipValidator OPTIONAL
329     * @return void
330     * @see http://www.iana.org/cctld/specifications-policies-cctlds-01apr02.htm  Technical Specifications for ccTLDs
331     */
332    public function __construct($options = array())
333    {
334        if ($options instanceof Zend_Config) {
335            $options = $options->toArray();
336        } else if (!is_array($options)) {
337            $options = func_get_args();
338            $temp['allow'] = array_shift($options);
339            if (!empty($options)) {
340                $temp['idn'] = array_shift($options);
341            }
342
343            if (!empty($options)) {
344                $temp['tld'] = array_shift($options);
345            }
346
347            if (!empty($options)) {
348                $temp['ip'] = array_shift($options);
349            }
350
351            $options = $temp;
352        }
353
354        $options += $this->_options;
355        $this->setOptions($options);
356    }
357
358    /**
359     * Returns all set options
360     *
361     * @return array
362     */
363    public function getOptions()
364    {
365        return $this->_options;
366    }
367
368    /**
369     * Sets the options for this validator
370     *
371     * @param array $options
372     * @return Zend_Validate_Hostname
373     */
374    public function setOptions($options)
375    {
376        if (array_key_exists('allow', $options)) {
377            $this->setAllow($options['allow']);
378        }
379
380        if (array_key_exists('idn', $options)) {
381            $this->setValidateIdn($options['idn']);
382        }
383
384        if (array_key_exists('tld', $options)) {
385            $this->setValidateTld($options['tld']);
386        }
387
388        if (array_key_exists('ip', $options)) {
389            $this->setIpValidator($options['ip']);
390        }
391
392        return $this;
393    }
394
395    /**
396     * Returns the set ip validator
397     *
398     * @return Zend_Validate_Ip
399     */
400    public function getIpValidator()
401    {
402        return $this->_options['ip'];
403    }
404
405    /**
406     * @param Zend_Validate_Ip $ipValidator OPTIONAL
407     * @return void;
408     */
409    public function setIpValidator(Zend_Validate_Ip $ipValidator = null)
410    {
411        if ($ipValidator === null) {
412            $ipValidator = new Zend_Validate_Ip();
413        }
414
415        $this->_options['ip'] = $ipValidator;
416        return $this;
417    }
418
419    /**
420     * Returns the allow option
421     *
422     * @return integer
423     */
424    public function getAllow()
425    {
426        return $this->_options['allow'];
427    }
428
429    /**
430     * Sets the allow option
431     *
432     * @param  integer $allow
433     * @return Zend_Validate_Hostname Provides a fluent interface
434     */
435    public function setAllow($allow)
436    {
437        $this->_options['allow'] = $allow;
438        return $this;
439    }
440
441    /**
442     * Returns the set idn option
443     *
444     * @return boolean
445     */
446    public function getValidateIdn()
447    {
448        return $this->_options['idn'];
449    }
450
451    /**
452     * Set whether IDN domains are validated
453     *
454     * This only applies when DNS hostnames are validated
455     *
456     * @param boolean $allowed Set allowed to true to validate IDNs, and false to not validate them
457     */
458    public function setValidateIdn ($allowed)
459    {
460        $this->_options['idn'] = (bool) $allowed;
461        return $this;
462    }
463
464    /**
465     * Returns the set tld option
466     *
467     * @return boolean
468     */
469    public function getValidateTld()
470    {
471        return $this->_options['tld'];
472    }
473
474    /**
475     * Set whether the TLD element of a hostname is validated
476     *
477     * This only applies when DNS hostnames are validated
478     *
479     * @param boolean $allowed Set allowed to true to validate TLDs, and false to not validate them
480     */
481    public function setValidateTld ($allowed)
482    {
483        $this->_options['tld'] = (bool) $allowed;
484        return $this;
485    }
486
487    /**
488     * Defined by Zend_Validate_Interface
489     *
490     * Returns true if and only if the $value is a valid hostname with respect to the current allow option
491     *
492     * @param  string $value
493     * @throws Zend_Validate_Exception if a fatal error occurs for validation process
494     * @return boolean
495     */
496    public function isValid($value)
497    {
498        if (!is_string($value)) {
499            $this->_error(self::INVALID);
500            return false;
501        }
502
503        $this->_setValue($value);
504        // Check input against IP address schema
505        if (preg_match('/^[0-9a-f:.]*$/i', $value) &&
506            $this->_options['ip']->setTranslator($this->getTranslator())->isValid($value)) {
507            if (!($this->_options['allow'] & self::ALLOW_IP)) {
508                $this->_error(self::IP_ADDRESS_NOT_ALLOWED);
509                return false;
510            } else {
511                return true;
512            }
513        }
514
515        // RFC3986 3.2.2 states:
516        // 
517        //     The rightmost domain label of a fully qualified domain name
518        //     in DNS may be followed by a single "." and should be if it is 
519        //     necessary to distinguish between the complete domain name and
520        //     some local domain.
521        //     
522        // (see ZF-6363)
523        
524        // Local hostnames are allowed to be partitial (ending '.')
525        if ($this->_options['allow'] & self::ALLOW_LOCAL) {
526            if (substr($value, -1) === '.') {
527                $value = substr($value, 0, -1);
528                if (substr($value, -1) === '.') {
529                    // Empty hostnames (ending '..') are not allowed
530                    $this->_error(self::INVALID_LOCAL_NAME);
531                    return false;
532                }
533            }
534        }
535
536        $domainParts = explode('.', $value);
537
538        // Prevent partitial IP V4 adresses (ending '.')
539        if ((count($domainParts) == 4) && preg_match('/^[0-9.a-e:.]*$/i', $value) &&
540            $this->_options['ip']->setTranslator($this->getTranslator())->isValid($value)) {
541            $this->_error(self::INVALID_LOCAL_NAME);
542        }
543
544        // Check input against DNS hostname schema
545        if ((count($domainParts) > 1) && (strlen($value) >= 4) && (strlen($value) <= 254)) {
546            $status = false;
547
548            $origenc = iconv_get_encoding('internal_encoding');
549            iconv_set_encoding('internal_encoding', 'UTF-8');
550            do {
551                // First check TLD
552                $matches = array();
553                if (preg_match('/([^.]{2,10})$/i', end($domainParts), $matches) ||
554                    (end($domainParts) == 'ایران') || (end($domainParts) == '中国') ||
555                    (end($domainParts) == '公司') || (end($domainParts) == '网络')) {
556
557                    reset($domainParts);
558
559                    // Hostname characters are: *(label dot)(label dot label); max 254 chars
560                    // label: id-prefix [*ldh{61} id-prefix]; max 63 chars
561                    // id-prefix: alpha / digit
562                    // ldh: alpha / digit / dash
563
564                    // Match TLD against known list
565                    $this->_tld = strtolower($matches[1]);
566                    if ($this->_options['tld']) {
567                        if (!in_array($this->_tld, $this->_validTlds)) {
568                            $this->_error(self::UNKNOWN_TLD);
569                            $status = false;
570                            break;
571                        }
572                    }
573
574                    /**
575                     * Match against IDN hostnames
576                     * Note: Keep label regex short to avoid issues with long patterns when matching IDN hostnames
577                     * @see Zend_Validate_Hostname_Interface
578                     */
579                    $regexChars = array(0 => '/^[a-z0-9\x2d]{1,63}$/i');
580                    if ($this->_options['idn'] &&  isset($this->_validIdns[strtoupper($this->_tld)])) {
581                        if (is_string($this->_validIdns[strtoupper($this->_tld)])) {
582                            $regexChars += include(dirname(__FILE__) . DIRECTORY_SEPARATOR . $this->_validIdns[strtoupper($this->_tld)]);
583                        } else {
584                            $regexChars += $this->_validIdns[strtoupper($this->_tld)];
585                        }
586                    }
587
588                    // Check each hostname part
589                    $check = 0;
590                    foreach ($domainParts as $domainPart) {
591                        // Decode Punycode domainnames to IDN
592                        if (strpos($domainPart, 'xn--') === 0) {
593                            $domainPart = $this->decodePunycode(substr($domainPart, 4));
594                            if ($domainPart === false) {
595                                return false;
596                            }
597                        }
598
599                        // Check dash (-) does not start, end or appear in 3rd and 4th positions
600                        if ((strpos($domainPart, '-') === 0)
601                            || ((strlen($domainPart) > 2) && (strpos($domainPart, '-', 2) == 2) && (strpos($domainPart, '-', 3) == 3))
602                            || (strpos($domainPart, '-') === (strlen($domainPart) - 1))) {
603                                $this->_error(self::INVALID_DASH);
604                            $status = false;
605                            break 2;
606                        }
607
608                        // Check each domain part
609                        $checked = false;
610                        foreach($regexChars as $regexKey => $regexChar) {
611                            $status = @preg_match($regexChar, $domainPart);
612                            if ($status > 0) {
613                                $length = 63;
614                                if (array_key_exists(strtoupper($this->_tld), $this->_idnLength)
615                                    && (array_key_exists($regexKey, $this->_idnLength[strtoupper($this->_tld)]))) {
616                                    $length = $this->_idnLength[strtoupper($this->_tld)];
617                                }
618
619                                if (iconv_strlen($domainPart, 'UTF-8') > $length) {
620                                    $this->_error(self::INVALID_HOSTNAME);
621                                } else {
622                                    $checked = true;
623                                    break;
624                                }
625                            }
626                        }
627
628                        if ($checked) {
629                            ++$check;
630                        }
631                    }
632
633                    // If one of the labels doesn't match, the hostname is invalid
634                    if ($check !== count($domainParts)) {
635                        $this->_error(self::INVALID_HOSTNAME_SCHEMA);
636                        $status = false;
637                    }
638                } else {
639                    // Hostname not long enough
640                    $this->_error(self::UNDECIPHERABLE_TLD);
641                    $status = false;
642                }
643            } while (false);
644
645            iconv_set_encoding('internal_encoding', $origenc);
646            // If the input passes as an Internet domain name, and domain names are allowed, then the hostname
647            // passes validation
648            if ($status && ($this->_options['allow'] & self::ALLOW_DNS)) {
649                return true;
650            }
651        } else if ($this->_options['allow'] & self::ALLOW_DNS) {
652            $this->_error(self::INVALID_HOSTNAME);
653        }
654
655        // Check for URI Syntax (RFC3986)
656        if ($this->_options['allow'] & self::ALLOW_URI) {
657            if (preg_match("/^([a-zA-Z0-9-._~!$&\'()*+,;=]|%[[:xdigit:]]{2}){1,254}$/i", $value)) {
658                return true;
659            } else {
660                $this->_error(self::INVALID_URI);
661            }
662        }
663
664        // Check input against local network name schema; last chance to pass validation
665        $regexLocal = '/^(([a-zA-Z0-9\x2d]{1,63}\x2e)*[a-zA-Z0-9\x2d]{1,63}[\x2e]{0,1}){1,254}$/';
666        $status = @preg_match($regexLocal, $value);
667
668        // If the input passes as a local network name, and local network names are allowed, then the
669        // hostname passes validation
670        $allowLocal = $this->_options['allow'] & self::ALLOW_LOCAL;
671        if ($status && $allowLocal) {
672            return true;
673        }
674
675        // If the input does not pass as a local network name, add a message
676        if (!$status) {
677            $this->_error(self::INVALID_LOCAL_NAME);
678        }
679
680        // If local network names are not allowed, add a message
681        if ($status && !$allowLocal) {
682            $this->_error(self::LOCAL_NAME_NOT_ALLOWED);
683        }
684
685        return false;
686    }
687
688    /**
689     * Decodes a punycode encoded string to it's original utf8 string
690     * In case of a decoding failure the original string is returned
691     *
692     * @param  string $encoded Punycode encoded string to decode
693     * @return string
694     */
695    protected function decodePunycode($encoded)
696    {
697        $found = preg_match('/([^a-z0-9\x2d]{1,10})$/i', $encoded);
698        if (empty($encoded) || ($found > 0)) {
699            // no punycode encoded string, return as is
700            $this->_error(self::CANNOT_DECODE_PUNYCODE);
701            return false;
702        }
703
704        $separator = strrpos($encoded, '-');
705        if ($separator > 0) {
706            for ($x = 0; $x < $separator; ++$x) {
707                // prepare decoding matrix
708                $decoded[] = ord($encoded[$x]);
709            }
710        } else {
711            $this->_error(self::CANNOT_DECODE_PUNYCODE);
712            return false;
713        }
714
715        $lengthd = count($decoded);
716        $lengthe = strlen($encoded);
717
718        // decoding
719        $init  = true;
720        $base  = 72;
721        $index = 0;
722        $char  = 0x80;
723
724        for ($indexe = ($separator) ? ($separator + 1) : 0; $indexe < $lengthe; ++$lengthd) {
725            for ($old_index = $index, $pos = 1, $key = 36; 1 ; $key += 36) {
726                $hex   = ord($encoded[$indexe++]);
727                $digit = ($hex - 48 < 10) ? $hex - 22
728                       : (($hex - 65 < 26) ? $hex - 65
729                       : (($hex - 97 < 26) ? $hex - 97
730                       : 36));
731
732                $index += $digit * $pos;
733                $tag    = ($key <= $base) ? 1 : (($key >= $base + 26) ? 26 : ($key - $base));
734                if ($digit < $tag) {
735                    break;
736                }
737
738                $pos = (int) ($pos * (36 - $tag));
739            }
740
741            $delta   = intval($init ? (($index - $old_index) / 700) : (($index - $old_index) / 2));
742            $delta  += intval($delta / ($lengthd + 1));
743            for ($key = 0; $delta > 910 / 2; $key += 36) {
744                $delta = intval($delta / 35);
745            }
746
747            $base   = intval($key + 36 * $delta / ($delta + 38));
748            $init   = false;
749            $char  += (int) ($index / ($lengthd + 1));
750            $index %= ($lengthd + 1);
751            if ($lengthd > 0) {
752                for ($i = $lengthd; $i > $index; $i--) {
753                    $decoded[$i] = $decoded[($i - 1)];
754                }
755            }
756
757            $decoded[$index++] = $char;
758        }
759
760        // convert decoded ucs4 to utf8 string
761        foreach ($decoded as $key => $value) {
762            if ($value < 128) {
763                $decoded[$key] = chr($value);
764            } elseif ($value < (1 << 11)) {
765                $decoded[$key]  = chr(192 + ($value >> 6));
766                $decoded[$key] .= chr(128 + ($value & 63));
767            } elseif ($value < (1 << 16)) {
768                $decoded[$key]  = chr(224 + ($value >> 12));
769                $decoded[$key] .= chr(128 + (($value >> 6) & 63));
770                $decoded[$key] .= chr(128 + ($value & 63));
771            } elseif ($value < (1 << 21)) {
772                $decoded[$key]  = chr(240 + ($value >> 18));
773                $decoded[$key] .= chr(128 + (($value >> 12) & 63));
774                $decoded[$key] .= chr(128 + (($value >> 6) & 63));
775                $decoded[$key] .= chr(128 + ($value & 63));
776            } else {
777                $this->_error(self::CANNOT_DECODE_PUNYCODE);
778                return false;
779            }
780        }
781
782        return implode($decoded);
783    }
784}