PageRenderTime 79ms CodeModel.GetById 20ms app.highlight 51ms RepoModel.GetById 1ms app.codeStats 0ms

/library/Zend/Validator/Hostname.php

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