]> CyberLeo.Net >> Repos - Github/sugarcrm.git/blob - Zend/Validate/Hostname.php
Release 6.5.0
[Github/sugarcrm.git] / Zend / Validate / Hostname.php
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-2010 Zend Technologies USA Inc. (http://www.zend.com)
18  * @license    http://framework.zend.com/license/new-bsd     New BSD License
19
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-2010 Zend Technologies USA Inc. (http://www.zend.com)
45  * @license    http://framework.zend.com/license/new-bsd     New BSD License
46  */
47 class Zend_Validate_Hostname extends Zend_Validate_Abstract
48 {
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';
59
60     /**
61      * @var array
62      */
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",
74     );
75
76     /**
77      * @var array
78      */
79     protected $_messageVariables = array(
80         'tld' => '_tld'
81     );
82
83     /**
84      * Allows Internet domain names (e.g., example.com)
85      */
86     const ALLOW_DNS   = 1;
87
88     /**
89      * Allows IP addresses
90      */
91     const ALLOW_IP    = 2;
92
93     /**
94      * Allows local network names (e.g., localhost, www.localdomain)
95      */
96     const ALLOW_LOCAL = 4;
97
98     /**
99      * Allows all types of hostnames
100      */
101     const ALLOW_ALL   = 7;
102
103     /**
104      * Array of valid top-level-domains
105      *
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
108      * @var array
109      */
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'
130     );
131
132     /**
133      * @var string
134      */
135     protected $_tld;
136
137     /**
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
184      *
185      * @var array
186      */
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'
290     );
291
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),
306     );
307
308     protected $_options = array(
309         'allow' => self::ALLOW_DNS,
310         'idn'   => true,
311         'tld'   => true,
312         'ip'    => null
313     );
314
315     /**
316      * Sets validator options
317      *
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
322      * @return void
323      * @see http://www.iana.org/cctld/specifications-policies-cctlds-01apr02.htm  Technical Specifications for ccTLDs
324      */
325     public function __construct($options = array())
326     {
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);
334             }
335
336             if (!empty($options)) {
337                 $temp['tld'] = array_shift($options);
338             }
339
340             if (!empty($options)) {
341                 $temp['ip'] = array_shift($options);
342             }
343
344             $options = $temp;
345         }
346
347         $options += $this->_options;
348         $this->setOptions($options);
349     }
350
351     /**
352      * Returns all set options
353      *
354      * @return array
355      */
356     public function getOptions()
357     {
358         return $this->_options;
359     }
360
361     /**
362      * Sets the options for this validator
363      *
364      * @param array $options
365      * @return Zend_Validate_Hostname
366      */
367     public function setOptions($options)
368     {
369         if (array_key_exists('allow', $options)) {
370             $this->setAllow($options['allow']);
371         }
372
373         if (array_key_exists('idn', $options)) {
374             $this->setValidateIdn($options['idn']);
375         }
376
377         if (array_key_exists('tld', $options)) {
378             $this->setValidateTld($options['tld']);
379         }
380
381         if (array_key_exists('ip', $options)) {
382             $this->setIpValidator($options['ip']);
383         }
384
385         return $this;
386     }
387
388     /**
389      * Returns the set ip validator
390      *
391      * @return Zend_Validate_Ip
392      */
393     public function getIpValidator()
394     {
395         return $this->_options['ip'];
396     }
397
398     /**
399      * @param Zend_Validate_Ip $ipValidator OPTIONAL
400      * @return void;
401      */
402     public function setIpValidator(Zend_Validate_Ip $ipValidator = null)
403     {
404         if ($ipValidator === null) {
405             $ipValidator = new Zend_Validate_Ip();
406         }
407
408         $this->_options['ip'] = $ipValidator;
409         return $this;
410     }
411
412     /**
413      * Returns the allow option
414      *
415      * @return integer
416      */
417     public function getAllow()
418     {
419         return $this->_options['allow'];
420     }
421
422     /**
423      * Sets the allow option
424      *
425      * @param  integer $allow
426      * @return Zend_Validate_Hostname Provides a fluent interface
427      */
428     public function setAllow($allow)
429     {
430         $this->_options['allow'] = $allow;
431         return $this;
432     }
433
434     /**
435      * Returns the set idn option
436      *
437      * @return boolean
438      */
439     public function getValidateIdn()
440     {
441         return $this->_options['idn'];
442     }
443
444     /**
445      * Set whether IDN domains are validated
446      *
447      * This only applies when DNS hostnames are validated
448      *
449      * @param boolean $allowed Set allowed to true to validate IDNs, and false to not validate them
450      */
451     public function setValidateIdn ($allowed)
452     {
453         $this->_options['idn'] = (bool) $allowed;
454         return $this;
455     }
456
457     /**
458      * Returns the set tld option
459      *
460      * @return boolean
461      */
462     public function getValidateTld()
463     {
464         return $this->_options['tld'];
465     }
466
467     /**
468      * Set whether the TLD element of a hostname is validated
469      *
470      * This only applies when DNS hostnames are validated
471      *
472      * @param boolean $allowed Set allowed to true to validate TLDs, and false to not validate them
473      */
474     public function setValidateTld ($allowed)
475     {
476         $this->_options['tld'] = (bool) $allowed;
477         return $this;
478     }
479
480     /**
481      * Defined by Zend_Validate_Interface
482      *
483      * Returns true if and only if the $value is a valid hostname with respect to the current allow option
484      *
485      * @param  string $value
486      * @throws Zend_Validate_Exception if a fatal error occurs for validation process
487      * @return boolean
488      */
489     public function isValid($value)
490     {
491         if (!is_string($value)) {
492             $this->_error(self::INVALID);
493             return false;
494         }
495
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);
502                 return false;
503             } else {
504                 return true;
505             }
506         }
507
508         // Check input against DNS hostname schema
509         $domainParts = explode('.', $value);
510         if ((count($domainParts) > 1) && (strlen($value) >= 4) && (strlen($value) <= 254)) {
511             $status = false;
512
513             $origenc = iconv_get_encoding('internal_encoding');
514             iconv_set_encoding('internal_encoding', 'UTF-8');
515             do {
516                 // First check TLD
517                 $matches = array();
518                 if (preg_match('/([^.]{2,10})$/i', end($domainParts), $matches) ||
519                     (end($domainParts) == 'ایران') || (end($domainParts) == '中国') ||
520                     (end($domainParts) == '公司') || (end($domainParts) == '网络')) {
521
522                     reset($domainParts);
523
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
528
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);
534                             $status = false;
535                             break;
536                         }
537                     }
538
539                     /**
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
543                      */
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)]);
548                         } else {
549                             $regexChars += $this->_validIdns[strtoupper($this->_tld)];
550                         }
551                     }
552
553                     // Check each hostname part
554                     $check = 0;
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) {
560                                 return false;
561                             }
562                         }
563
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);
569                             $status = false;
570                             break 2;
571                         }
572
573                         // Check each domain part
574                         $checked = false;
575                         foreach($regexChars as $regexKey => $regexChar) {
576                             $status = @preg_match($regexChar, $domainPart);
577                             if ($status > 0) {
578                                 $length = 63;
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)];
582                                 }
583
584                                 if (iconv_strlen($domainPart, 'UTF-8') > $length) {
585                                     $this->_error(self::INVALID_HOSTNAME);
586                                 } else {
587                                     $checked = true;
588                                     break;
589                                 }
590                             }
591                         }
592
593                         if ($checked) {
594                             ++$check;
595                         }
596                     }
597
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);
601                         $status = false;
602                     }
603                 } else {
604                     // Hostname not long enough
605                     $this->_error(self::UNDECIPHERABLE_TLD);
606                     $status = false;
607                 }
608             } while (false);
609
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
612             // passes validation
613             if ($status && ($this->_options['allow'] & self::ALLOW_DNS)) {
614                 return true;
615             }
616         } else if ($this->_options['allow'] & self::ALLOW_DNS) {
617             $this->_error(self::INVALID_HOSTNAME);
618         }
619
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);
623
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) {
628             return true;
629         }
630
631         // If the input does not pass as a local network name, add a message
632         if (!$status) {
633             $this->_error(self::INVALID_LOCAL_NAME);
634         }
635
636         // If local network names are not allowed, add a message
637         if ($status && !$allowLocal) {
638             $this->_error(self::LOCAL_NAME_NOT_ALLOWED);
639         }
640
641         return false;
642     }
643
644     /**
645      * Decodes a punycode encoded string to it's original utf8 string
646      * In case of a decoding failure the original string is returned
647      *
648      * @param  string $encoded Punycode encoded string to decode
649      * @return string
650      */
651     protected function decodePunycode($encoded)
652     {
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);
657             return false;
658         }
659
660         $separator = strrpos($encoded, '-');
661         if ($separator > 0) {
662             for ($x = 0; $x < $separator; ++$x) {
663                 // prepare decoding matrix
664                 $decoded[] = ord($encoded[$x]);
665             }
666         } else {
667             $this->_error(self::CANNOT_DECODE_PUNYCODE);
668             return false;
669         }
670
671         $lengthd = count($decoded);
672         $lengthe = strlen($encoded);
673
674         // decoding
675         $init  = true;
676         $base  = 72;
677         $index = 0;
678         $char  = 0x80;
679
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
686                        : 36));
687
688                 $index += $digit * $pos;
689                 $tag    = ($key <= $base) ? 1 : (($key >= $base + 26) ? 26 : ($key - $base));
690                 if ($digit < $tag) {
691                     break;
692                 }
693
694                 $pos = (int) ($pos * (36 - $tag));
695             }
696
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);
701             }
702
703             $base   = intval($key + 36 * $delta / ($delta + 38));
704             $init   = false;
705             $char  += (int) ($index / ($lengthd + 1));
706             $index %= ($lengthd + 1);
707             if ($lengthd > 0) {
708                 for ($i = $lengthd; $i > $index; $i--) {
709                     $decoded[$i] = $decoded[($i - 1)];
710                 }
711             }
712
713             $decoded[$index++] = $char;
714         }
715
716         // convert decoded ucs4 to utf8 string
717         foreach ($decoded as $key => $value) {
718             if ($value < 128) {
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));
732             } else {
733                 $this->_error(self::CANNOT_DECODE_PUNYCODE);
734                 return false;
735             }
736         }
737
738         return implode($decoded);
739     }
740 }