CUrlValidator.php 6.0 KB

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