PageRenderTime 6ms CodeModel.GetById 8ms app.highlight 26ms RepoModel.GetById 1ms app.codeStats 0ms

/third_party/Zend/Validate/Hostname.php

https://bitbucket.org/pfernandez/testlink1.9.6
PHP | 740 lines | 445 code | 66 blank | 229 comment | 73 complexity | 5b8060c02a35fbb35716611e687f6047 MD5 | raw file
  1<?php
  2/**
  3 * Zend Framework
  4 *
  5 * LICENSE
  6 *
  7 * This source file is subject to the new BSD license that is bundled
  8 * with this package in the file LICENSE.txt.
  9 * It is also available through the world-wide-web at this URL:
 10 * http://framework.zend.com/license/new-bsd
 11 * If you did not receive a copy of the license and are unable to
 12 * obtain it through the world-wide-web, please send an email
 13 * to license@zend.com so we can send you a copy immediately.
 14 *
 15 * @category   Zend
 16 * @package    Zend_Validate
 17 * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
 18 * @license    http://framework.zend.com/license/new-bsd     New BSD License
 19 * @version    $Id: Hostname.php,v 1.1 2010/05/02 16:27:51 franciscom Exp $
 20 */
 21
 22/**
 23 * @see Zend_Validate_Abstract
 24 */
 25require_once 'Zend/Validate/Abstract.php';
 26
 27/**
 28 * @see Zend_Validate_Ip
 29 */
 30require_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 */
 47class 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????????????????????????????????????????�?????�????????????�]{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}