1: <?php
2: /**
3: * CUrlValidator class file.
4: *
5: * @author Qiang Xue <qiang.xue@gmail.com>
6: * @link http://www.yiiframework.com/
7: * @copyright 2008-2013 Yii Software LLC
8: * @license http://www.yiiframework.com/license/
9: */
10:
11: /**
12: * CUrlValidator validates that the attribute value is a valid http or https URL.
13: *
14: * @author Qiang Xue <qiang.xue@gmail.com>
15: * @package system.validators
16: * @since 1.0
17: */
18: class CUrlValidator extends CValidator
19: {
20: /**
21: * @var string the regular expression used to validate the attribute value.
22: * Since version 1.1.7 the pattern may contain a {schemes} token that will be replaced
23: * by a regular expression which represents the {@see validSchemes}.
24: */
25: public $pattern='/^{schemes}:\/\/(([A-Z0-9][A-Z0-9_-]*)(\.[A-Z0-9][A-Z0-9_-]*)+)/i';
26: /**
27: * @var array list of URI schemes which should be considered valid. By default, http and https
28: * are considered to be valid schemes.
29: * @since 1.1.7
30: **/
31: public $validSchemes=array('http','https');
32: /**
33: * @var string the default URI scheme. If the input doesn't contain the scheme part, the default
34: * scheme will be prepended to it (thus changing the input). Defaults to null, meaning a URL must
35: * contain the scheme part.
36: * @since 1.1.7
37: **/
38: public $defaultScheme;
39: /**
40: * @var boolean whether the attribute value can be null or empty. Defaults to true,
41: * meaning that if the attribute is empty, it is considered valid.
42: */
43: public $allowEmpty=true;
44: /**
45: * @var boolean whether validation process should care about IDN (internationalized domain names). Default
46: * value is false which means that validation of URLs containing IDN will always fail.
47: * @since 1.1.13
48: */
49: public $validateIDN=false;
50:
51: /**
52: * Validates the attribute of the object.
53: * If there is any error, the error message is added to the object.
54: * @param CModel $object the object being validated
55: * @param string $attribute the attribute being validated
56: */
57: protected function validateAttribute($object,$attribute)
58: {
59: $value=$object->$attribute;
60: if($this->allowEmpty && $this->isEmpty($value))
61: return;
62: if(($value=$this->validateValue($value))!==false)
63: $object->$attribute=$value;
64: else
65: {
66: $message=$this->message!==null?$this->message:Yii::t('yii','{attribute} is not a valid URL.');
67: $this->addError($object,$attribute,$message);
68: }
69: }
70:
71: /**
72: * Validates a static value to see if it is a valid URL.
73: * Note that this method does not respect {@link allowEmpty} property.
74: * This method is provided so that you can call it directly without going through the model validation rule mechanism.
75: * @param string $value the value to be validated
76: * @return mixed false if the the value is not a valid URL, otherwise the possibly modified value ({@see defaultScheme})
77: * @since 1.1.1
78: */
79: public function validateValue($value)
80: {
81: if(is_string($value) && strlen($value)<2000) // make sure the length is limited to avoid DOS attacks
82: {
83: if($this->defaultScheme!==null && strpos($value,'://')===false)
84: $value=$this->defaultScheme.'://'.$value;
85:
86: if($this->validateIDN)
87: $value=$this->encodeIDN($value);
88:
89: if(strpos($this->pattern,'{schemes}')!==false)
90: $pattern=str_replace('{schemes}','('.implode('|',$this->validSchemes).')',$this->pattern);
91: else
92: $pattern=$this->pattern;
93:
94: if(preg_match($pattern,$value))
95: return $this->validateIDN ? $this->decodeIDN($value) : $value;
96: }
97: return false;
98: }
99:
100: /**
101: * Returns the JavaScript needed for performing client-side validation.
102: * @param CModel $object the data object being validated
103: * @param string $attribute the name of the attribute to be validated.
104: * @return string the client-side validation script.
105: * @see CActiveForm::enableClientValidation
106: * @since 1.1.7
107: */
108: public function clientValidateAttribute($object,$attribute)
109: {
110: if($this->validateIDN)
111: {
112: Yii::app()->getClientScript()->registerCoreScript('punycode');
113: // punycode.js works only with the domains - so we have to extract it before punycoding
114: $validateIDN='
115: var info = value.match(/^(.+:\/\/|)([^/]+)/);
116: if (info)
117: value = info[1] + punycode.toASCII(info[2]);
118: ';
119: }
120: else
121: $validateIDN='';
122:
123: $message=$this->message!==null ? $this->message : Yii::t('yii','{attribute} is not a valid URL.');
124: $message=strtr($message, array(
125: '{attribute}'=>$object->getAttributeLabel($attribute),
126: ));
127:
128: if(strpos($this->pattern,'{schemes}')!==false)
129: $pattern=str_replace('{schemes}','('.implode('|',$this->validSchemes).')',$this->pattern);
130: else
131: $pattern=$this->pattern;
132:
133: $js="
134: $validateIDN
135: if(!value.match($pattern)) {
136: messages.push(".CJSON::encode($message).");
137: }
138: ";
139: if($this->defaultScheme!==null)
140: {
141: $js="
142: if(!value.match(/:\\/\\//)) {
143: value=".CJSON::encode($this->defaultScheme)."+'://'+value;
144: }
145: $js
146: ";
147: }
148:
149: if($this->allowEmpty)
150: {
151: $js="
152: if(jQuery.trim(value)!='') {
153: $js
154: }
155: ";
156: }
157:
158: return $js;
159: }
160:
161: /**
162: * Converts given IDN to the punycode.
163: * @param string $value IDN to be converted.
164: * @return string resulting punycode.
165: * @since 1.1.13
166: */
167: private function encodeIDN($value)
168: {
169: if(preg_match_all('/^(.*):\/\/([^\/]+)(.*)$/',$value,$matches))
170: {
171: if(function_exists('idn_to_ascii'))
172: $value=$matches[1][0].'://'.idn_to_ascii($matches[2][0]).$matches[3][0];
173: else
174: {
175: require_once(Yii::getPathOfAlias('system.vendors.Net_IDNA2.Net').DIRECTORY_SEPARATOR.'IDNA2.php');
176: $idna=new Net_IDNA2();
177: $value=$matches[1][0].'://'.@$idna->encode($matches[2][0]).$matches[3][0];
178: }
179: }
180: return $value;
181: }
182:
183: /**
184: * Converts given punycode to the IDN.
185: * @param string $value punycode to be converted.
186: * @return string resulting IDN.
187: * @since 1.1.13
188: */
189: private function decodeIDN($value)
190: {
191: if(preg_match_all('/^(.*):\/\/([^\/]+)(.*)$/',$value,$matches))
192: {
193: if(function_exists('idn_to_utf8'))
194: $value=$matches[1][0].'://'.idn_to_utf8($matches[2][0]).$matches[3][0];
195: else
196: {
197: require_once(Yii::getPathOfAlias('system.vendors.Net_IDNA2.Net').DIRECTORY_SEPARATOR.'IDNA2.php');
198: $idna=new Net_IDNA2();
199: $value=$matches[1][0].'://'.@$idna->decode($matches[2][0]).$matches[3][0];
200: }
201: }
202: return $value;
203: }
204: }
205: