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.
16 * @package Zend_Validate
17 * @copyright Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
18 * @license http://framework.zend.com/license/new-bsd New BSD License
23 * @see Zend_Validate_Abstract
25 require_once 'Zend/Validate/Abstract.php';
28 * @see Zend_Validate_Ip
30 require_once 'Zend/Validate/Ip.php';
33 * Please note there are two standalone test scripts for testing IDN characters due to problems
36 * The first is tests/Zend/Validate/HostnameTestStandalone.php which is designed to be run on
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.
43 * @package Zend_Validate
44 * @copyright Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
45 * @license http://framework.zend.com/license/new-bsd New BSD License
47 class Zend_Validate_Hostname extends Zend_Validate_Abstract
49 const INVALID = 'hostnameInvalid';
50 const IP_ADDRESS_NOT_ALLOWED = 'hostnameIpAddressNotAllowed';
51 const UNKNOWN_TLD = 'hostnameUnknownTld';
52 const INVALID_DASH = 'hostnameDashCharacter';
53 const INVALID_HOSTNAME_SCHEMA = 'hostnameInvalidHostnameSchema';
54 const UNDECIPHERABLE_TLD = 'hostnameUndecipherableTld';
55 const INVALID_HOSTNAME = 'hostnameInvalidHostname';
56 const INVALID_LOCAL_NAME = 'hostnameInvalidLocalName';
57 const LOCAL_NAME_NOT_ALLOWED = 'hostnameLocalNameNotAllowed';
58 const CANNOT_DECODE_PUNYCODE = 'hostnameCannotDecodePunycode';
63 protected $_messageTemplates = array(
64 self::INVALID => "Invalid type given, value should be a string",
65 self::IP_ADDRESS_NOT_ALLOWED => "'%value%' appears to be an IP address, but IP addresses are not allowed",
66 self::UNKNOWN_TLD => "'%value%' appears to be a DNS hostname but cannot match TLD against known list",
67 self::INVALID_DASH => "'%value%' appears to be a DNS hostname but contains a dash in an invalid position",
68 self::INVALID_HOSTNAME_SCHEMA => "'%value%' appears to be a DNS hostname but cannot match against hostname schema for TLD '%tld%'",
69 self::UNDECIPHERABLE_TLD => "'%value%' appears to be a DNS hostname but cannot extract TLD part",
70 self::INVALID_HOSTNAME => "'%value%' does not match the expected structure for a DNS hostname",
71 self::INVALID_LOCAL_NAME => "'%value%' does not appear to be a valid local network name",
72 self::LOCAL_NAME_NOT_ALLOWED => "'%value%' appears to be a local network name but local network names are not allowed",
73 self::CANNOT_DECODE_PUNYCODE => "'%value%' appears to be a DNS hostname but the given punycode notation cannot be decoded",
79 protected $_messageVariables = array(
84 * Allows Internet domain names (e.g., example.com)
94 * Allows local network names (e.g., localhost, www.localdomain)
96 const ALLOW_LOCAL = 4;
99 * Allows all types of hostnames
104 * Array of valid top-level-domains
106 * @see ftp://data.iana.org/TLD/tlds-alpha-by-domain.txt List of all TLDs by domain
107 * @see http://www.iana.org/domains/root/db/ Official list of supported TLDs
110 protected $_validTlds = array(
111 'ac', 'ad', 'ae', 'aero', 'af', 'ag', 'ai', 'al', 'am', 'an', 'ao', 'aq', 'ar', 'arpa',
112 'as', 'asia', 'at', 'au', 'aw', 'ax', 'az', 'ba', 'bb', 'bd', 'be', 'bf', 'bg', 'bh', 'bi',
113 'biz', 'bj', 'bm', 'bn', 'bo', 'br', 'bs', 'bt', 'bv', 'bw', 'by', 'bz', 'ca', 'cat', 'cc',
114 'cd', 'cf', 'cg', 'ch', 'ci', 'ck', 'cl', 'cm', 'cn', 'co', 'com', 'coop', 'cr', 'cu',
115 'cv', 'cx', 'cy', 'cz', 'de', 'dj', 'dk', 'dm', 'do', 'dz', 'ec', 'edu', 'ee', 'eg', 'er',
116 'es', 'et', 'eu', 'fi', 'fj', 'fk', 'fm', 'fo', 'fr', 'ga', 'gb', 'gd', 'ge', 'gf', 'gg',
117 'gh', 'gi', 'gl', 'gm', 'gn', 'gov', 'gp', 'gq', 'gr', 'gs', 'gt', 'gu', 'gw', 'gy', 'hk',
118 'hm', 'hn', 'hr', 'ht', 'hu', 'id', 'ie', 'il', 'im', 'in', 'info', 'int', 'io', 'iq',
119 'ir', 'is', 'it', 'je', 'jm', 'jo', 'jobs', 'jp', 'ke', 'kg', 'kh', 'ki', 'km', 'kn', 'kp',
120 'kr', 'kw', 'ky', 'kz', 'la', 'lb', 'lc', 'li', 'lk', 'lr', 'ls', 'lt', 'lu', 'lv', 'ly',
121 'ma', 'mc', 'md', 'me', 'mg', 'mh', 'mil', 'mk', 'ml', 'mm', 'mn', 'mo', 'mobi', 'mp',
122 'mq', 'mr', 'ms', 'mt', 'mu', 'museum', 'mv', 'mw', 'mx', 'my', 'mz', 'na', 'name', 'nc',
123 'ne', 'net', 'nf', 'ng', 'ni', 'nl', 'no', 'np', 'nr', 'nu', 'nz', 'om', 'org', 'pa', 'pe',
124 'pf', 'pg', 'ph', 'pk', 'pl', 'pm', 'pn', 'pr', 'pro', 'ps', 'pt', 'pw', 'py', 'qa', 're',
125 'ro', 'rs', 'ru', 'rw', 'sa', 'sb', 'sc', 'sd', 'se', 'sg', 'sh', 'si', 'sj', 'sk', 'sl',
126 'sm', 'sn', 'so', 'sr', 'st', 'su', 'sv', 'sy', 'sz', 'tc', 'td', 'tel', 'tf', 'tg', 'th',
127 'tj', 'tk', 'tl', 'tm', 'tn', 'to', 'tp', 'tr', 'travel', 'tt', 'tv', 'tw', 'tz', 'ua',
128 'ug', 'uk', 'um', 'us', 'uy', 'uz', 'va', 'vc', 've', 'vg', 'vi', 'vn', 'vu', 'wf', 'ws',
129 'ye', 'yt', 'yu', 'za', 'zm', 'zw'
138 * Array for valid Idns
139 * @see http://www.iana.org/domains/idn-tables/ Official list of supported IDN Chars
140 * (.AC) Ascension Island http://www.nic.ac/pdf/AC-IDN-Policy.pdf
141 * (.AR) Argentinia http://www.nic.ar/faqidn.html
142 * (.AS) American Samoa http://www.nic.as/idn/chars.cfm
143 * (.AT) Austria http://www.nic.at/en/service/technical_information/idn/charset_converter/
144 * (.BIZ) International http://www.iana.org/domains/idn-tables/
145 * (.BR) Brazil http://registro.br/faq/faq6.html
146 * (.BV) Bouvett Island http://www.norid.no/domeneregistrering/idn/idn_nyetegn.en.html
147 * (.CAT) Catalan http://www.iana.org/domains/idn-tables/tables/cat_ca_1.0.html
148 * (.CH) Switzerland https://nic.switch.ch/reg/ocView.action?res=EF6GW2JBPVTG67DLNIQXU234MN6SC33JNQQGI7L6#anhang1
149 * (.CL) Chile http://www.iana.org/domains/idn-tables/tables/cl_latn_1.0.html
150 * (.COM) International http://www.verisign.com/information-services/naming-services/internationalized-domain-names/index.html
151 * (.DE) Germany http://www.denic.de/en/domains/idns/liste.html
152 * (.DK) Danmark http://www.dk-hostmaster.dk/index.php?id=151
153 * (.ES) Spain https://www.nic.es/media/2008-05/1210147705287.pdf
154 * (.FI) Finland http://www.ficora.fi/en/index/palvelut/fiverkkotunnukset/aakkostenkaytto.html
155 * (.GR) Greece https://grweb.ics.forth.gr/CharacterTable1_en.jsp
156 * (.HU) Hungary http://www.domain.hu/domain/English/szabalyzat/szabalyzat.html
157 * (.INFO) International http://www.nic.info/info/idn
158 * (.IO) British Indian Ocean Territory http://www.nic.io/IO-IDN-Policy.pdf
159 * (.IR) Iran http://www.nic.ir/Allowable_Characters_dot-iran
160 * (.IS) Iceland http://www.isnic.is/domain/rules.php
161 * (.KR) Korea http://www.iana.org/domains/idn-tables/tables/kr_ko-kr_1.0.html
162 * (.LI) Liechtenstein https://nic.switch.ch/reg/ocView.action?res=EF6GW2JBPVTG67DLNIQXU234MN6SC33JNQQGI7L6#anhang1
163 * (.LT) Lithuania http://www.domreg.lt/static/doc/public/idn_symbols-en.pdf
164 * (.MD) Moldova http://www.register.md/
165 * (.MUSEUM) International http://www.iana.org/domains/idn-tables/tables/museum_latn_1.0.html
166 * (.NET) International http://www.verisign.com/information-services/naming-services/internationalized-domain-names/index.html
167 * (.NO) Norway http://www.norid.no/domeneregistrering/idn/idn_nyetegn.en.html
168 * (.NU) Niue http://www.worldnames.net/
169 * (.ORG) International http://www.pir.org/index.php?db=content/FAQs&tbl=FAQs_Registrant&id=2
170 * (.PE) Peru https://www.nic.pe/nuevas_politicas_faq_2.php
171 * (.PL) Poland http://www.dns.pl/IDN/allowed_character_sets.pdf
172 * (.PR) Puerto Rico http://www.nic.pr/idn_rules.asp
173 * (.PT) Portugal https://online.dns.pt/dns_2008/do?com=DS;8216320233;111;+PAGE(4000058)+K-CAT-CODIGO(C.125)+RCNT(100);
174 * (.RU) Russia http://www.iana.org/domains/idn-tables/tables/ru_ru-ru_1.0.html
175 * (.SA) Saudi Arabia http://www.iana.org/domains/idn-tables/tables/sa_ar_1.0.html
176 * (.SE) Sweden http://www.iis.se/english/IDN_campaignsite.shtml?lang=en
177 * (.SH) Saint Helena http://www.nic.sh/SH-IDN-Policy.pdf
178 * (.SJ) Svalbard and Jan Mayen http://www.norid.no/domeneregistrering/idn/idn_nyetegn.en.html
179 * (.TH) Thailand http://www.iana.org/domains/idn-tables/tables/th_th-th_1.0.html
180 * (.TM) Turkmenistan http://www.nic.tm/TM-IDN-Policy.pdf
181 * (.TR) Turkey https://www.nic.tr/index.php
182 * (.VE) Venice http://www.iana.org/domains/idn-tables/tables/ve_es_1.0.html
183 * (.VN) Vietnam http://www.vnnic.vn/english/5-6-300-2-2-04-20071115.htm#1.%20Introduction
187 protected $_validIdns = array(
188 'AC' => array(1 => '/^[\x{002d}0-9a-zà-öø-ÿāăąćĉċčďđēėęěĝġģĥħīįĵķĺļľŀłńņňŋőœŕŗřśŝşšţťŧūŭůűųŵŷźżž]{1,63}$/iu'),
189 'AR' => array(1 => '/^[\x{002d}0-9a-zà-ãç-êìíñ-õü]{1,63}$/iu'),
190 'AS' => array(1 => '/^[\x{002d}0-9a-zà-öø-ÿāăąćĉċčďđēĕėęěĝğġģĥħĩīĭįıĵķĸĺļľłńņňŋōŏőœŕŗřśŝşšţťŧũūŭůűųŵŷźż]{1,63}$/iu'),
191 'AT' => array(1 => '/^[\x{002d}0-9a-zà-öø-ÿœšž]{1,63}$/iu'),
192 'BIZ' => 'Hostname/Biz.php',
193 'BR' => array(1 => '/^[\x{002d}0-9a-zà-ãçéíó-õúü]{1,63}$/iu'),
194 'BV' => array(1 => '/^[\x{002d}0-9a-zàáä-éêñ-ôöøüčđńŋšŧž]{1,63}$/iu'),
195 'CAT' => array(1 => '/^[\x{002d}0-9a-z·àç-éíïòóúü]{1,63}$/iu'),
196 'CH' => array(1 => '/^[\x{002d}0-9a-zà-öø-ÿœ]{1,63}$/iu'),
197 'CL' => array(1 => '/^[\x{002d}0-9a-záéíñóúü]{1,63}$/iu'),
198 'CN' => 'Hostname/Cn.php',
199 'COM' => 'Zend/Validate/Hostname/Com.php',
200 'DE' => array(1 => '/^[\x{002d}0-9a-zà-öø-ÿăąāćĉčċďđĕěėęēğĝġģĥħĭĩįīıĵķĺľļłńňņŋŏőōœĸŕřŗśŝšşťţŧŭůűũųūŵŷźžż]{1,63}$/iu'),
201 'DK' => array(1 => '/^[\x{002d}0-9a-zäéöü]{1,63}$/iu'),
202 'ES' => array(1 => '/^[\x{002d}0-9a-zàáçèéíïñòóúü·]{1,63}$/iu'),
203 'EU' => array(1 => '/^[\x{002d}0-9a-zà-öø-ÿ]{1,63}$/iu',
204 2 => '/^[\x{002d}0-9a-zāăąćĉċčďđēĕėęěĝğġģĥħĩīĭįıĵķĺļľŀłńņňʼnŋōŏőœŕŗřśŝšťŧũūŭůűųŵŷźżž]{1,63}$/iu',
205 3 => '/^[\x{002d}0-9a-zșț]{1,63}$/iu',
206 4 => '/^[\x{002d}0-9a-zΐάέήίΰαβγδεζηθικλμνξοπρςστυφχψωϊϋόύώ]{1,63}$/iu',
207 5 => '/^[\x{002d}0-9a-zабвгдежзийклмнопрстуфхцчшщъыьэюя]{1,63}$/iu',
208 6 => '/^[\x{002d}0-9a-zἀ-ἇἐ-ἕἠ-ἧἰ-ἷὀ-ὅὐ-ὗὠ-ὧὰ-ώᾀ-ᾇᾐ-ᾗᾠ-ᾧᾰ-ᾴᾶᾷῂῃῄῆῇῐ-ΐῖῗῠ-ῧῲῳῴῶῷ]{1,63}$/iu'),
209 'FI' => array(1 => '/^[\x{002d}0-9a-zäåö]{1,63}$/iu'),
210 'GR' => array(1 => '/^[\x{002d}0-9a-zΆΈΉΊΌΎ-ΡΣ-ώἀ-ἕἘ-Ἕἠ-ὅὈ-Ὅὐ-ὗὙὛὝὟ-ώᾀ-ᾴᾶ-ᾼῂῃῄῆ-ῌῐ-ΐῖ-Ίῠ-Ῥῲῳῴῶ-ῼ]{1,63}$/iu'),
211 'HK' => 'Zend/Validate/Hostname/Cn.php',
212 'HU' => array(1 => '/^[\x{002d}0-9a-záéíóöúüőű]{1,63}$/iu'),
213 'INFO'=> array(1 => '/^[\x{002d}0-9a-zäåæéöøü]{1,63}$/iu',
214 2 => '/^[\x{002d}0-9a-záéíóöúüőű]{1,63}$/iu',
215 3 => '/^[\x{002d}0-9a-záæéíðóöúýþ]{1,63}$/iu',
216 4 => '/^[\x{AC00}-\x{D7A3}]{1,17}$/iu',
217 5 => '/^[\x{002d}0-9a-zāčēģīķļņōŗšūž]{1,63}$/iu',
218 6 => '/^[\x{002d}0-9a-ząčėęįšūųž]{1,63}$/iu',
219 7 => '/^[\x{002d}0-9a-zóąćęłńśźż]{1,63}$/iu',
220 8 => '/^[\x{002d}0-9a-záéíñóúü]{1,63}$/iu'),
221 'IO' => array(1 => '/^[\x{002d}0-9a-zà-öø-ÿăąāćĉčċďđĕěėęēğĝġģĥħĭĩįīıĵķĺľļłńňņŋŏőōœĸŕřŗśŝšşťţŧŭůűũųūŵŷźžż]{1,63}$/iu'),
222 'IS' => array(1 => '/^[\x{002d}0-9a-záéýúíóþæöð]{1,63}$/iu'),
223 'JP' => 'Zend/Validate/Hostname/Jp.php',
224 'KR' => array(1 => '/^[\x{AC00}-\x{D7A3}]{1,17}$/iu'),
225 'LI' => array(1 => '/^[\x{002d}0-9a-zà-öø-ÿœ]{1,63}$/iu'),
226 'LT' => array(1 => '/^[\x{002d}0-9ąčęėįšųūž]{1,63}$/iu'),
227 'MD' => array(1 => '/^[\x{002d}0-9ăâîşţ]{1,63}$/iu'),
228 'MUSEUM' => array(1 => '/^[\x{002d}0-9a-zà-öø-ÿāăąćċčďđēėęěğġģħīįıķĺļľłńņňŋōőœŕŗřśşšţťŧūůűųŵŷźżžǎǐǒǔ\x{01E5}\x{01E7}\x{01E9}\x{01EF}ə\x{0292}ẁẃẅỳ]{1,63}$/iu'),
229 'NET' => 'Zend/Validate/Hostname/Com.php',
230 'NO' => array(1 => '/^[\x{002d}0-9a-zàáä-éêñ-ôöøüčđńŋšŧž]{1,63}$/iu'),
231 'NU' => 'Zend/Validate/Hostname/Com.php',
232 'ORG' => array(1 => '/^[\x{002d}0-9a-záéíñóúü]{1,63}$/iu',
233 2 => '/^[\x{002d}0-9a-zóąćęłńśźż]{1,63}$/iu',
234 3 => '/^[\x{002d}0-9a-záäåæéëíðóöøúüýþ]{1,63}$/iu',
235 4 => '/^[\x{002d}0-9a-záéíóöúüőű]{1,63}$/iu',
236 5 => '/^[\x{002d}0-9a-ząčėęįšūųž]{1,63}$/iu',
237 6 => '/^[\x{AC00}-\x{D7A3}]{1,17}$/iu',
238 7 => '/^[\x{002d}0-9a-zāčēģīķļņōŗšūž]{1,63}$/iu'),
239 'PE' => array(1 => '/^[\x{002d}0-9a-zñáéíóúü]{1,63}$/iu'),
240 'PL' => array(1 => '/^[\x{002d}0-9a-zāčēģīķļņōŗšūž]{1,63}$/iu',
241 2 => '/^[\x{002d}а-ик-ш\x{0450}ѓѕјљњќџ]{1,63}$/iu',
242 3 => '/^[\x{002d}0-9a-zâîăşţ]{1,63}$/iu',
243 4 => '/^[\x{002d}0-9а-яё\x{04C2}]{1,63}$/iu',
244 5 => '/^[\x{002d}0-9a-zàáâèéêìíîòóôùúûċġħż]{1,63}$/iu',
245 6 => '/^[\x{002d}0-9a-zàäåæéêòóôöøü]{1,63}$/iu',
246 7 => '/^[\x{002d}0-9a-zóąćęłńśźż]{1,63}$/iu',
247 8 => '/^[\x{002d}0-9a-zàáâãçéêíòóôõúü]{1,63}$/iu',
248 9 => '/^[\x{002d}0-9a-zâîăşţ]{1,63}$/iu',
249 10=> '/^[\x{002d}0-9a-záäéíóôúýčďĺľňŕšťž]{1,63}$/iu',
250 11=> '/^[\x{002d}0-9a-zçë]{1,63}$/iu',
251 12=> '/^[\x{002d}0-9а-ик-шђјљњћџ]{1,63}$/iu',
252 13=> '/^[\x{002d}0-9a-zćčđšž]{1,63}$/iu',
253 14=> '/^[\x{002d}0-9a-zâçöûüğış]{1,63}$/iu',
254 15=> '/^[\x{002d}0-9a-záéíñóúü]{1,63}$/iu',
255 16=> '/^[\x{002d}0-9a-zäõöüšž]{1,63}$/iu',
256 17=> '/^[\x{002d}0-9a-zĉĝĥĵŝŭ]{1,63}$/iu',
257 18=> '/^[\x{002d}0-9a-zâäéëîô]{1,63}$/iu',
258 19=> '/^[\x{002d}0-9a-zàáâäåæçèéêëìíîïðñòôöøùúûüýćčłńřśš]{1,63}$/iu',
259 20=> '/^[\x{002d}0-9a-zäåæõöøüšž]{1,63}$/iu',
260 21=> '/^[\x{002d}0-9a-zàáçèéìíòóùú]{1,63}$/iu',
261 22=> '/^[\x{002d}0-9a-zàáéíóöúüőű]{1,63}$/iu',
262 23=> '/^[\x{002d}0-9ΐά-ώ]{1,63}$/iu',
263 24=> '/^[\x{002d}0-9a-zàáâåæçèéêëðóôöøüþœ]{1,63}$/iu',
264 25=> '/^[\x{002d}0-9a-záäéíóöúüýčďěňřšťůž]{1,63}$/iu',
265 26=> '/^[\x{002d}0-9a-z·àçèéíïòóúü]{1,63}$/iu',
266 27=> '/^[\x{002d}0-9а-ъьюя\x{0450}\x{045D}]{1,63}$/iu',
267 28=> '/^[\x{002d}0-9а-яёіў]{1,63}$/iu',
268 29=> '/^[\x{002d}0-9a-ząčėęįšūųž]{1,63}$/iu',
269 30=> '/^[\x{002d}0-9a-záäåæéëíðóöøúüýþ]{1,63}$/iu',
270 31=> '/^[\x{002d}0-9a-zàâæçèéêëîïñôùûüÿœ]{1,63}$/iu',
271 32=> '/^[\x{002d}0-9а-щъыьэюяёєіїґ]{1,63}$/iu',
272 33=> '/^[\x{002d}0-9א-ת]{1,63}$/iu'),
273 'PR' => array(1 => '/^[\x{002d}0-9a-záéíóúñäëïüöâêîôûàèùæçœãõ]{1,63}$/iu'),
274 'PT' => array(1 => '/^[\x{002d}0-9a-záàâãçéêíóôõú]{1,63}$/iu'),
275 'RU' => array(1 => '/^[\x{002d}0-9а-яё]{1,63}$/iu'),
276 'SA' => array(1 => '/^[\x{002d}.0-9\x{0621}-\x{063A}\x{0641}-\x{064A}\x{0660}-\x{0669}]{1,63}$/iu'),
277 'SE' => array(1 => '/^[\x{002d}0-9a-zäåéöü]{1,63}$/iu'),
278 'SH' => array(1 => '/^[\x{002d}0-9a-zà-öø-ÿăąāćĉčċďđĕěėęēğĝġģĥħĭĩįīıĵķĺľļłńňņŋŏőōœĸŕřŗśŝšşťţŧŭůűũųūŵŷźžż]{1,63}$/iu'),
279 'SJ' => array(1 => '/^[\x{002d}0-9a-zàáä-éêñ-ôöøüčđńŋšŧž]{1,63}$/iu'),
280 'TH' => array(1 => '/^[\x{002d}0-9a-z\x{0E01}-\x{0E3A}\x{0E40}-\x{0E4D}\x{0E50}-\x{0E59}]{1,63}$/iu'),
281 'TM' => array(1 => '/^[\x{002d}0-9a-zà-öø-ÿāăąćĉċčďđēėęěĝġģĥħīįĵķĺļľŀłńņňŋőœŕŗřśŝşšţťŧūŭůűųŵŷźżž]{1,63}$/iu'),
282 'TW' => 'Zend/Validate/Hostname/Cn.php',
283 'TR' => array(1 => '/^[\x{002d}0-9a-zğıüşöç]{1,63}$/iu'),
284 'VE' => array(1 => '/^[\x{002d}0-9a-záéíóúüñ]{1,63}$/iu'),
285 'VN' => array(1 => '/^[ÀÁÂÃÈÉÊÌÍÒÓÔÕÙÚÝàáâãèéêìíòóôõùúýĂăĐđĨĩŨũƠơƯư\x{1EA0}-\x{1EF9}]{1,63}$/iu'),
286 'ایران' => 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'),
287 '中国' => 'Zend/Validate/Hostname/Cn.php',
288 '公司' => 'Zend/Validate/Hostname/Cn.php',
289 '网络' => 'Zend/Validate/Hostname/Cn.php'
292 protected $_idnLength = array(
293 'BIZ' => array(5 => 17, 11 => 15, 12 => 20),
294 'CN' => array(1 => 20),
295 'COM' => array(3 => 17, 5 => 20),
296 'HK' => array(1 => 15),
297 'INFO'=> array(4 => 17),
298 'KR' => array(1 => 17),
299 'NET' => array(3 => 17, 5 => 20),
300 'ORG' => array(6 => 17),
301 'TW' => array(1 => 20),
302 'ایران' => array(1 => 30),
303 '中国' => array(1 => 20),
304 '公司' => array(1 => 20),
305 '网络' => array(1 => 20),
308 protected $_options = array(
309 'allow' => self::ALLOW_DNS,
316 * Sets validator options
318 * @param integer $allow OPTIONAL Set what types of hostname to allow (default ALLOW_DNS)
319 * @param boolean $validateIdn OPTIONAL Set whether IDN domains are validated (default true)
320 * @param boolean $validateTld OPTIONAL Set whether the TLD element of a hostname is validated (default true)
321 * @param Zend_Validate_Ip $ipValidator OPTIONAL
323 * @see http://www.iana.org/cctld/specifications-policies-cctlds-01apr02.htm Technical Specifications for ccTLDs
325 public function __construct($options = array())
327 if ($options instanceof Zend_Config) {
328 $options = $options->toArray();
329 } else if (!is_array($options)) {
330 $options = func_get_args();
331 $temp['allow'] = array_shift($options);
332 if (!empty($options)) {
333 $temp['idn'] = array_shift($options);
336 if (!empty($options)) {
337 $temp['tld'] = array_shift($options);
340 if (!empty($options)) {
341 $temp['ip'] = array_shift($options);
347 $options += $this->_options;
348 $this->setOptions($options);
352 * Returns all set options
356 public function getOptions()
358 return $this->_options;
362 * Sets the options for this validator
364 * @param array $options
365 * @return Zend_Validate_Hostname
367 public function setOptions($options)
369 if (array_key_exists('allow', $options)) {
370 $this->setAllow($options['allow']);
373 if (array_key_exists('idn', $options)) {
374 $this->setValidateIdn($options['idn']);
377 if (array_key_exists('tld', $options)) {
378 $this->setValidateTld($options['tld']);
381 if (array_key_exists('ip', $options)) {
382 $this->setIpValidator($options['ip']);
389 * Returns the set ip validator
391 * @return Zend_Validate_Ip
393 public function getIpValidator()
395 return $this->_options['ip'];
399 * @param Zend_Validate_Ip $ipValidator OPTIONAL
402 public function setIpValidator(Zend_Validate_Ip $ipValidator = null)
404 if ($ipValidator === null) {
405 $ipValidator = new Zend_Validate_Ip();
408 $this->_options['ip'] = $ipValidator;
413 * Returns the allow option
417 public function getAllow()
419 return $this->_options['allow'];
423 * Sets the allow option
425 * @param integer $allow
426 * @return Zend_Validate_Hostname Provides a fluent interface
428 public function setAllow($allow)
430 $this->_options['allow'] = $allow;
435 * Returns the set idn option
439 public function getValidateIdn()
441 return $this->_options['idn'];
445 * Set whether IDN domains are validated
447 * This only applies when DNS hostnames are validated
449 * @param boolean $allowed Set allowed to true to validate IDNs, and false to not validate them
451 public function setValidateIdn ($allowed)
453 $this->_options['idn'] = (bool) $allowed;
458 * Returns the set tld option
462 public function getValidateTld()
464 return $this->_options['tld'];
468 * Set whether the TLD element of a hostname is validated
470 * This only applies when DNS hostnames are validated
472 * @param boolean $allowed Set allowed to true to validate TLDs, and false to not validate them
474 public function setValidateTld ($allowed)
476 $this->_options['tld'] = (bool) $allowed;
481 * Defined by Zend_Validate_Interface
483 * Returns true if and only if the $value is a valid hostname with respect to the current allow option
485 * @param string $value
486 * @throws Zend_Validate_Exception if a fatal error occurs for validation process
489 public function isValid($value)
491 if (!is_string($value)) {
492 $this->_error(self::INVALID);
496 $this->_setValue($value);
497 // Check input against IP address schema
498 if (preg_match('/^[0-9.a-e:.]*$/i', $value) &&
499 $this->_options['ip']->setTranslator($this->getTranslator())->isValid($value)) {
500 if (!($this->_options['allow'] & self::ALLOW_IP)) {
501 $this->_error(self::IP_ADDRESS_NOT_ALLOWED);
508 // Check input against DNS hostname schema
509 $domainParts = explode('.', $value);
510 if ((count($domainParts) > 1) && (strlen($value) >= 4) && (strlen($value) <= 254)) {
513 $origenc = iconv_get_encoding('internal_encoding');
514 iconv_set_encoding('internal_encoding', 'UTF-8');
518 if (preg_match('/([^.]{2,10})$/i', end($domainParts), $matches) ||
519 (end($domainParts) == 'ایران') || (end($domainParts) == '中国') ||
520 (end($domainParts) == '公司') || (end($domainParts) == '网络')) {
524 // Hostname characters are: *(label dot)(label dot label); max 254 chars
525 // label: id-prefix [*ldh{61} id-prefix]; max 63 chars
526 // id-prefix: alpha / digit
527 // ldh: alpha / digit / dash
529 // Match TLD against known list
530 $this->_tld = strtolower($matches[1]);
531 if ($this->_options['tld']) {
532 if (!in_array($this->_tld, $this->_validTlds)) {
533 $this->_error(self::UNKNOWN_TLD);
540 * Match against IDN hostnames
541 * Note: Keep label regex short to avoid issues with long patterns when matching IDN hostnames
542 * @see Zend_Validate_Hostname_Interface
544 $regexChars = array(0 => '/^[a-z0-9\x2d]{1,63}$/i');
545 if ($this->_options['idn'] && isset($this->_validIdns[strtoupper($this->_tld)])) {
546 if (is_string($this->_validIdns[strtoupper($this->_tld)])) {
547 $regexChars += include($this->_validIdns[strtoupper($this->_tld)]);
549 $regexChars += $this->_validIdns[strtoupper($this->_tld)];
553 // Check each hostname part
555 foreach ($domainParts as $domainPart) {
556 // Decode Punycode domainnames to IDN
557 if (strpos($domainPart, 'xn--') === 0) {
558 $domainPart = $this->decodePunycode(substr($domainPart, 4));
559 if ($domainPart === false) {
564 // Check dash (-) does not start, end or appear in 3rd and 4th positions
565 if ((strpos($domainPart, '-') === 0)
566 || ((strlen($domainPart) > 2) && (strpos($domainPart, '-', 2) == 2) && (strpos($domainPart, '-', 3) == 3))
567 || (strpos($domainPart, '-') === (strlen($domainPart) - 1))) {
568 $this->_error(self::INVALID_DASH);
573 // Check each domain part
575 foreach($regexChars as $regexKey => $regexChar) {
576 $status = @preg_match($regexChar, $domainPart);
579 if (array_key_exists(strtoupper($this->_tld), $this->_idnLength)
580 && (array_key_exists($regexKey, $this->_idnLength[strtoupper($this->_tld)]))) {
581 $length = $this->_idnLength[strtoupper($this->_tld)];
584 if (iconv_strlen($domainPart, 'UTF-8') > $length) {
585 $this->_error(self::INVALID_HOSTNAME);
598 // If one of the labels doesn't match, the hostname is invalid
599 if ($check !== count($domainParts)) {
600 $this->_error(self::INVALID_HOSTNAME_SCHEMA);
604 // Hostname not long enough
605 $this->_error(self::UNDECIPHERABLE_TLD);
610 iconv_set_encoding('internal_encoding', $origenc);
611 // If the input passes as an Internet domain name, and domain names are allowed, then the hostname
613 if ($status && ($this->_options['allow'] & self::ALLOW_DNS)) {
616 } else if ($this->_options['allow'] & self::ALLOW_DNS) {
617 $this->_error(self::INVALID_HOSTNAME);
620 // Check input against local network name schema; last chance to pass validation
621 $regexLocal = '/^(([a-zA-Z0-9\x2d]{1,63}\x2e)*[a-zA-Z0-9\x2d]{1,63}){1,254}$/';
622 $status = @preg_match($regexLocal, $value);
624 // If the input passes as a local network name, and local network names are allowed, then the
625 // hostname passes validation
626 $allowLocal = $this->_options['allow'] & self::ALLOW_LOCAL;
627 if ($status && $allowLocal) {
631 // If the input does not pass as a local network name, add a message
633 $this->_error(self::INVALID_LOCAL_NAME);
636 // If local network names are not allowed, add a message
637 if ($status && !$allowLocal) {
638 $this->_error(self::LOCAL_NAME_NOT_ALLOWED);
645 * Decodes a punycode encoded string to it's original utf8 string
646 * In case of a decoding failure the original string is returned
648 * @param string $encoded Punycode encoded string to decode
651 protected function decodePunycode($encoded)
653 $found = preg_match('/([^a-z0-9\x2d]{1,10})$/i', $encoded);
654 if (empty($encoded) || ($found > 0)) {
655 // no punycode encoded string, return as is
656 $this->_error(self::CANNOT_DECODE_PUNYCODE);
660 $separator = strrpos($encoded, '-');
661 if ($separator > 0) {
662 for ($x = 0; $x < $separator; ++$x) {
663 // prepare decoding matrix
664 $decoded[] = ord($encoded[$x]);
667 $this->_error(self::CANNOT_DECODE_PUNYCODE);
671 $lengthd = count($decoded);
672 $lengthe = strlen($encoded);
680 for ($indexe = ($separator) ? ($separator + 1) : 0; $indexe < $lengthe; ++$lengthd) {
681 for ($old_index = $index, $pos = 1, $key = 36; 1 ; $key += 36) {
682 $hex = ord($encoded[$indexe++]);
683 $digit = ($hex - 48 < 10) ? $hex - 22
684 : (($hex - 65 < 26) ? $hex - 65
685 : (($hex - 97 < 26) ? $hex - 97
688 $index += $digit * $pos;
689 $tag = ($key <= $base) ? 1 : (($key >= $base + 26) ? 26 : ($key - $base));
694 $pos = (int) ($pos * (36 - $tag));
697 $delta = intval($init ? (($index - $old_index) / 700) : (($index - $old_index) / 2));
698 $delta += intval($delta / ($lengthd + 1));
699 for ($key = 0; $delta > 910 / 2; $key += 36) {
700 $delta = intval($delta / 35);
703 $base = intval($key + 36 * $delta / ($delta + 38));
705 $char += (int) ($index / ($lengthd + 1));
706 $index %= ($lengthd + 1);
708 for ($i = $lengthd; $i > $index; $i--) {
709 $decoded[$i] = $decoded[($i - 1)];
713 $decoded[$index++] = $char;
716 // convert decoded ucs4 to utf8 string
717 foreach ($decoded as $key => $value) {
719 $decoded[$key] = chr($value);
720 } elseif ($value < (1 << 11)) {
721 $decoded[$key] = chr(192 + ($value >> 6));
722 $decoded[$key] .= chr(128 + ($value & 63));
723 } elseif ($value < (1 << 16)) {
724 $decoded[$key] = chr(224 + ($value >> 12));
725 $decoded[$key] .= chr(128 + (($value >> 6) & 63));
726 $decoded[$key] .= chr(128 + ($value & 63));
727 } elseif ($value < (1 << 21)) {
728 $decoded[$key] = chr(240 + ($value >> 18));
729 $decoded[$key] .= chr(128 + (($value >> 12) & 63));
730 $decoded[$key] .= chr(128 + (($value >> 6) & 63));
731 $decoded[$key] .= chr(128 + ($value & 63));
733 $this->_error(self::CANNOT_DECODE_PUNYCODE);
738 return implode($decoded);