Commit 992e9028 by Qiang Xue

Merge pull request #280 from resurtm/validator-idn

Fixes #143. UrlValidator and EmailValidator IDN support.
parents 5dffc75f 9803b438
...@@ -69,11 +69,26 @@ ...@@ -69,11 +69,26 @@
"bin": [ "bin": [
"yii/yiic" "yii/yiic"
], ],
"repositories": [
{
"type": "package",
"package": {
"name": "bestiejs/punycode.js",
"version": "1.2.1",
"source": {
"url": "git://github.com/bestiejs/punycode.js.git",
"type": "git",
"reference": "1.2.1"
}
}
}
],
"require": { "require": {
"php": ">=5.3.0", "php": ">=5.3.0",
"michelf/php-markdown": "1.3", "michelf/php-markdown": "1.3",
"twig/twig": "1.12.*", "twig/twig": "1.12.*",
"smarty/smarty": "3.1.*", "smarty/smarty": "3.1.*",
"ezyang/htmlpurifier": "v4.5.0" "ezyang/htmlpurifier": "v4.5.0",
"bestiejs/punycode.js": "1.2.1"
} }
} }
...@@ -3,9 +3,19 @@ ...@@ -3,9 +3,19 @@
"This file locks the dependencies of your project to a known state", "This file locks the dependencies of your project to a known state",
"Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file" "Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file"
], ],
"hash": "7d46ce9c4d8d5f4ecae1611ea8f0b49c", "hash": "a8f949e337a229a4cfb41496a0071ef6",
"packages": [ "packages": [
{ {
"name": "bestiejs/punycode.js",
"version": "1.2.1",
"source": {
"type": "git",
"url": "git://github.com/bestiejs/punycode.js.git",
"reference": "1.2.1"
},
"type": "library"
},
{
"name": "ezyang/htmlpurifier", "name": "ezyang/htmlpurifier",
"version": "v4.5.0", "version": "v4.5.0",
"source": { "source": {
......
...@@ -26,7 +26,7 @@ return array( ...@@ -26,7 +26,7 @@ return array(
'js' => array( 'js' => array(
'yii.activeForm.js', 'yii.activeForm.js',
), ),
'depends' => array('yii', 'yii/validation'), 'depends' => array('yii'),
), ),
'yii/captcha' => array( 'yii/captcha' => array(
'sourcePath' => __DIR__ . '/assets', 'sourcePath' => __DIR__ . '/assets',
...@@ -42,4 +42,10 @@ return array( ...@@ -42,4 +42,10 @@ return array(
), ),
'depends' => array('yii'), 'depends' => array('yii'),
), ),
'punycode' => array(
'sourcePath' => __DIR__ . '/vendor/bestiejs/punycode.js',
'js' => array(
'punycode.min.js',
),
),
); );
...@@ -110,9 +110,19 @@ yii.validation = (function ($) { ...@@ -110,9 +110,19 @@ yii.validation = (function ($) {
return; return;
} }
var valid = value.match(options.pattern) && (!options.allowName || value.match(options.fullPattern)); var valid = true;
if (options.enableIDN) {
var regexp = /^(.*)@(.*)$/,
matches = regexp.exec(value);
if (matches === null) {
valid = false;
} else {
value = punycode.toASCII(matches[1]) + '@' + punycode.toASCII(matches[2]);
}
}
if (!valid) { if (!valid || !(value.match(options.pattern) && (!options.allowName || value.match(options.fullPattern)))) {
messages.push(options.message); messages.push(options.message);
} }
}, },
...@@ -126,7 +136,19 @@ yii.validation = (function ($) { ...@@ -126,7 +136,19 @@ yii.validation = (function ($) {
value = options.defaultScheme + '://' + value; value = options.defaultScheme + '://' + value;
} }
if (!value.match(options.pattern)) { var valid = true;
if (options.enableIDN) {
var regexp = /^([^:]+):\/\/([^\/]+)(.*)?/,
matches = regexp.exec(value);
if (matches === null) {
valid = false;
} else {
value = matches[1] + '://' + punycode.toASCII(matches[2]) + matches[3];
}
}
if (!valid || !value.match(options.pattern)) {
messages.push(options.message); messages.push(options.message);
} }
}, },
......
...@@ -79,9 +79,11 @@ class BooleanValidator extends Validator ...@@ -79,9 +79,11 @@ class BooleanValidator extends Validator
* Returns the JavaScript needed for performing client-side validation. * Returns the JavaScript needed for performing client-side validation.
* @param \yii\base\Model $object the data object being validated * @param \yii\base\Model $object the data object being validated
* @param string $attribute the name of the attribute to be validated. * @param string $attribute the name of the attribute to be validated.
* @param \yii\base\View $view the view object that is going to be used to render views or view files
* containing a model form with this validator applied.
* @return string the client-side validation script. * @return string the client-side validation script.
*/ */
public function clientValidateAttribute($object, $attribute) public function clientValidateAttribute($object, $attribute, $view)
{ {
$options = array( $options = array(
'trueValue' => $this->trueValue, 'trueValue' => $this->trueValue,
...@@ -100,6 +102,7 @@ class BooleanValidator extends Validator ...@@ -100,6 +102,7 @@ class BooleanValidator extends Validator
$options['strict'] = 1; $options['strict'] = 1;
} }
$view->registerAssetBundle('yii/validation');
return 'yii.validation.boolean(value, messages, ' . json_encode($options) . ');'; return 'yii.validation.boolean(value, messages, ' . json_encode($options) . ');';
} }
} }
...@@ -91,9 +91,11 @@ class CaptchaValidator extends Validator ...@@ -91,9 +91,11 @@ class CaptchaValidator extends Validator
* Returns the JavaScript needed for performing client-side validation. * Returns the JavaScript needed for performing client-side validation.
* @param \yii\base\Model $object the data object being validated * @param \yii\base\Model $object the data object being validated
* @param string $attribute the name of the attribute to be validated. * @param string $attribute the name of the attribute to be validated.
* @param \yii\base\View $view the view object that is going to be used to render views or view files
* containing a model form with this validator applied.
* @return string the client-side validation script. * @return string the client-side validation script.
*/ */
public function clientValidateAttribute($object, $attribute) public function clientValidateAttribute($object, $attribute, $view)
{ {
$captcha = $this->getCaptchaAction(); $captcha = $this->getCaptchaAction();
$code = $captcha->getVerifyCode(false); $code = $captcha->getVerifyCode(false);
...@@ -111,6 +113,7 @@ class CaptchaValidator extends Validator ...@@ -111,6 +113,7 @@ class CaptchaValidator extends Validator
$options['skipOnEmpty'] = 1; $options['skipOnEmpty'] = 1;
} }
$view->registerAssetBundle('yii/validation');
return 'yii.validation.captcha(value, messages, ' . json_encode($options) . ');'; return 'yii.validation.captcha(value, messages, ' . json_encode($options) . ');';
} }
} }
......
...@@ -178,9 +178,11 @@ class CompareValidator extends Validator ...@@ -178,9 +178,11 @@ class CompareValidator extends Validator
* @param \yii\base\Model $object the data object being validated * @param \yii\base\Model $object the data object being validated
* @param string $attribute the name of the attribute to be validated * @param string $attribute the name of the attribute to be validated
* @return string the client-side validation script * @return string the client-side validation script
* @param \yii\base\View $view the view object that is going to be used to render views or view files
* containing a model form with this validator applied.
* @throws InvalidConfigException if CompareValidator::operator is invalid * @throws InvalidConfigException if CompareValidator::operator is invalid
*/ */
public function clientValidateAttribute($object, $attribute) public function clientValidateAttribute($object, $attribute, $view)
{ {
$options = array('operator' => $this->operator); $options = array('operator' => $this->operator);
...@@ -203,6 +205,7 @@ class CompareValidator extends Validator ...@@ -203,6 +205,7 @@ class CompareValidator extends Validator
'{compareValue}' => $compareValue, '{compareValue}' => $compareValue,
))); )));
$view->registerAssetBundle('yii/validation');
return 'yii.validation.compare(value, messages, ' . json_encode($options) . ');'; return 'yii.validation.compare(value, messages, ' . json_encode($options) . ');';
} }
} }
...@@ -47,6 +47,12 @@ class EmailValidator extends Validator ...@@ -47,6 +47,12 @@ class EmailValidator extends Validator
* Defaults to false. * Defaults to false.
*/ */
public $checkPort = false; public $checkPort = false;
/**
* @var boolean whether validation process should take into account IDN (internationalized domain
* names). Defaults to false meaning that validation of emails containing IDN will always fail.
*/
public $enableIDN = false;
/** /**
* Initializes the validator. * Initializes the validator.
...@@ -81,10 +87,18 @@ class EmailValidator extends Validator ...@@ -81,10 +87,18 @@ class EmailValidator extends Validator
public function validateValue($value) public function validateValue($value)
{ {
// make sure string length is limited to avoid DOS attacks // make sure string length is limited to avoid DOS attacks
$valid = is_string($value) && strlen($value) <= 254 if (!is_string($value) || strlen($value) >= 255) {
&& (preg_match($this->pattern, $value) || $this->allowName && preg_match($this->fullPattern, $value)); return false;
}
if (($atPosition = strpos($value, '@')) === false) {
return false;
}
$domain = rtrim(substr($value, $atPosition + 1), '>');
if ($this->enableIDN) {
$value = idn_to_ascii(ltrim(substr($value, 0, $atPosition), '<')) . '@' . idn_to_ascii($domain);
}
$valid = preg_match($this->pattern, $value) || $this->allowName && preg_match($this->fullPattern, $value);
if ($valid) { if ($valid) {
$domain = rtrim(substr($value, strpos($value, '@') + 1), '>');
if ($this->checkMX && function_exists('checkdnsrr')) { if ($this->checkMX && function_exists('checkdnsrr')) {
$valid = checkdnsrr($domain, 'MX'); $valid = checkdnsrr($domain, 'MX');
} }
...@@ -99,9 +113,11 @@ class EmailValidator extends Validator ...@@ -99,9 +113,11 @@ class EmailValidator extends Validator
* Returns the JavaScript needed for performing client-side validation. * Returns the JavaScript needed for performing client-side validation.
* @param \yii\base\Model $object the data object being validated * @param \yii\base\Model $object the data object being validated
* @param string $attribute the name of the attribute to be validated. * @param string $attribute the name of the attribute to be validated.
* @param \yii\base\View $view the view object that is going to be used to render views or view files
* containing a model form with this validator applied.
* @return string the client-side validation script. * @return string the client-side validation script.
*/ */
public function clientValidateAttribute($object, $attribute) public function clientValidateAttribute($object, $attribute, $view)
{ {
$options = array( $options = array(
'pattern' => new JsExpression($this->pattern), 'pattern' => new JsExpression($this->pattern),
...@@ -111,11 +127,16 @@ class EmailValidator extends Validator ...@@ -111,11 +127,16 @@ class EmailValidator extends Validator
'{attribute}' => $object->getAttributeLabel($attribute), '{attribute}' => $object->getAttributeLabel($attribute),
'{value}' => $object->$attribute, '{value}' => $object->$attribute,
))), ))),
'enableIDN' => (boolean)$this->enableIDN,
); );
if ($this->skipOnEmpty) { if ($this->skipOnEmpty) {
$options['skipOnEmpty'] = 1; $options['skipOnEmpty'] = 1;
} }
$view->registerAssetBundle('yii/validation');
if ($this->enableIDN) {
$view->registerAssetBundle('punycode');
}
return 'yii.validation.email(value, messages, ' . Json::encode($options) . ');'; return 'yii.validation.email(value, messages, ' . Json::encode($options) . ');';
} }
} }
...@@ -79,12 +79,14 @@ class InlineValidator extends Validator ...@@ -79,12 +79,14 @@ class InlineValidator extends Validator
* *
* @param \yii\base\Model $object the data object being validated * @param \yii\base\Model $object the data object being validated
* @param string $attribute the name of the attribute to be validated. * @param string $attribute the name of the attribute to be validated.
* @param \yii\base\View $view the view object that is going to be used to render views or view files
* containing a model form with this validator applied.
* @return string the client-side validation script. Null if the validator does not support * @return string the client-side validation script. Null if the validator does not support
* client-side validation. * client-side validation.
* @see enableClientValidation * @see enableClientValidation
* @see \yii\web\ActiveForm::enableClientValidation * @see \yii\web\ActiveForm::enableClientValidation
*/ */
public function clientValidateAttribute($object, $attribute) public function clientValidateAttribute($object, $attribute, $view)
{ {
if ($this->clientValidate !== null) { if ($this->clientValidate !== null) {
$method = $this->clientValidate; $method = $this->clientValidate;
......
...@@ -114,9 +114,11 @@ class NumberValidator extends Validator ...@@ -114,9 +114,11 @@ class NumberValidator extends Validator
* Returns the JavaScript needed for performing client-side validation. * Returns the JavaScript needed for performing client-side validation.
* @param \yii\base\Model $object the data object being validated * @param \yii\base\Model $object the data object being validated
* @param string $attribute the name of the attribute to be validated. * @param string $attribute the name of the attribute to be validated.
* @param \yii\base\View $view the view object that is going to be used to render views or view files
* containing a model form with this validator applied.
* @return string the client-side validation script. * @return string the client-side validation script.
*/ */
public function clientValidateAttribute($object, $attribute) public function clientValidateAttribute($object, $attribute, $view)
{ {
$label = $object->getAttributeLabel($attribute); $label = $object->getAttributeLabel($attribute);
$value = $object->$attribute; $value = $object->$attribute;
...@@ -149,6 +151,7 @@ class NumberValidator extends Validator ...@@ -149,6 +151,7 @@ class NumberValidator extends Validator
$options['skipOnEmpty'] = 1; $options['skipOnEmpty'] = 1;
} }
$view->registerAssetBundle('yii/validation');
return 'yii.validation.number(value, messages, ' . Json::encode($options) . ');'; return 'yii.validation.number(value, messages, ' . Json::encode($options) . ');';
} }
} }
...@@ -81,9 +81,11 @@ class RangeValidator extends Validator ...@@ -81,9 +81,11 @@ class RangeValidator extends Validator
* Returns the JavaScript needed for performing client-side validation. * Returns the JavaScript needed for performing client-side validation.
* @param \yii\base\Model $object the data object being validated * @param \yii\base\Model $object the data object being validated
* @param string $attribute the name of the attribute to be validated. * @param string $attribute the name of the attribute to be validated.
* @param \yii\base\View $view the view object that is going to be used to render views or view files
* containing a model form with this validator applied.
* @return string the client-side validation script. * @return string the client-side validation script.
*/ */
public function clientValidateAttribute($object, $attribute) public function clientValidateAttribute($object, $attribute, $view)
{ {
$range = array(); $range = array();
foreach ($this->range as $value) { foreach ($this->range as $value) {
...@@ -101,6 +103,7 @@ class RangeValidator extends Validator ...@@ -101,6 +103,7 @@ class RangeValidator extends Validator
$options['skipOnEmpty'] = 1; $options['skipOnEmpty'] = 1;
} }
$view->registerAssetBundle('yii/validation');
return 'yii.validation.range(value, messages, ' . json_encode($options) . ');'; return 'yii.validation.range(value, messages, ' . json_encode($options) . ');';
} }
} }
...@@ -79,10 +79,12 @@ class RegularExpressionValidator extends Validator ...@@ -79,10 +79,12 @@ class RegularExpressionValidator extends Validator
* Returns the JavaScript needed for performing client-side validation. * Returns the JavaScript needed for performing client-side validation.
* @param \yii\base\Model $object the data object being validated * @param \yii\base\Model $object the data object being validated
* @param string $attribute the name of the attribute to be validated. * @param string $attribute the name of the attribute to be validated.
* @param \yii\base\View $view the view object that is going to be used to render views or view files
* containing a model form with this validator applied.
* @return string the client-side validation script. * @return string the client-side validation script.
* @throws InvalidConfigException if the "pattern" is not a valid regular expression * @throws InvalidConfigException if the "pattern" is not a valid regular expression
*/ */
public function clientValidateAttribute($object, $attribute) public function clientValidateAttribute($object, $attribute, $view)
{ {
$pattern = $this->pattern; $pattern = $this->pattern;
$pattern = preg_replace('/\\\\x\{?([0-9a-fA-F]+)\}?/', '\u$1', $pattern); $pattern = preg_replace('/\\\\x\{?([0-9a-fA-F]+)\}?/', '\u$1', $pattern);
...@@ -110,6 +112,7 @@ class RegularExpressionValidator extends Validator ...@@ -110,6 +112,7 @@ class RegularExpressionValidator extends Validator
$options['skipOnEmpty'] = 1; $options['skipOnEmpty'] = 1;
} }
$view->registerAssetBundle('yii/validation');
return 'yii.validation.regularExpression(value, messages, ' . Json::encode($options) . ');'; return 'yii.validation.regularExpression(value, messages, ' . Json::encode($options) . ');';
} }
} }
...@@ -102,9 +102,11 @@ class RequiredValidator extends Validator ...@@ -102,9 +102,11 @@ class RequiredValidator extends Validator
* Returns the JavaScript needed for performing client-side validation. * Returns the JavaScript needed for performing client-side validation.
* @param \yii\base\Model $object the data object being validated * @param \yii\base\Model $object the data object being validated
* @param string $attribute the name of the attribute to be validated. * @param string $attribute the name of the attribute to be validated.
* @param \yii\base\View $view the view object that is going to be used to render views or view files
* containing a model form with this validator applied.
* @return string the client-side validation script. * @return string the client-side validation script.
*/ */
public function clientValidateAttribute($object, $attribute) public function clientValidateAttribute($object, $attribute, $view)
{ {
$options = array(); $options = array();
if ($this->requiredValue !== null) { if ($this->requiredValue !== null) {
...@@ -124,6 +126,7 @@ class RequiredValidator extends Validator ...@@ -124,6 +126,7 @@ class RequiredValidator extends Validator
'{value}' => $object->$attribute, '{value}' => $object->$attribute,
))); )));
$view->registerAssetBundle('yii/validation');
return 'yii.validation.required(value, messages, ' . json_encode($options) . ');'; return 'yii.validation.required(value, messages, ' . json_encode($options) . ');';
} }
} }
...@@ -126,9 +126,11 @@ class StringValidator extends Validator ...@@ -126,9 +126,11 @@ class StringValidator extends Validator
* Returns the JavaScript needed for performing client-side validation. * Returns the JavaScript needed for performing client-side validation.
* @param \yii\base\Model $object the data object being validated * @param \yii\base\Model $object the data object being validated
* @param string $attribute the name of the attribute to be validated. * @param string $attribute the name of the attribute to be validated.
* @param \yii\base\View $view the view object that is going to be used to render views or view files
* containing a model form with this validator applied.
* @return string the client-side validation script. * @return string the client-side validation script.
*/ */
public function clientValidateAttribute($object, $attribute) public function clientValidateAttribute($object, $attribute, $view)
{ {
$label = $object->getAttributeLabel($attribute); $label = $object->getAttributeLabel($attribute);
$value = $object->$attribute; $value = $object->$attribute;
...@@ -168,6 +170,7 @@ class StringValidator extends Validator ...@@ -168,6 +170,7 @@ class StringValidator extends Validator
$options['skipOnEmpty'] = 1; $options['skipOnEmpty'] = 1;
} }
$view->registerAssetBundle('yii/validation');
return 'yii.validation.string(value, messages, ' . json_encode($options) . ');'; return 'yii.validation.string(value, messages, ' . json_encode($options) . ');';
} }
} }
......
...@@ -37,6 +37,12 @@ class UrlValidator extends Validator ...@@ -37,6 +37,12 @@ class UrlValidator extends Validator
* contain the scheme part. * contain the scheme part.
**/ **/
public $defaultScheme; public $defaultScheme;
/**
* @var boolean whether validation process should take into account IDN (internationalized
* domain names). Defaults to false meaning that validation of URLs containing IDN will always
* fail.
*/
public $enableIDN = false;
/** /**
...@@ -87,6 +93,12 @@ class UrlValidator extends Validator ...@@ -87,6 +93,12 @@ class UrlValidator extends Validator
$pattern = $this->pattern; $pattern = $this->pattern;
} }
if ($this->enableIDN) {
$value = preg_replace_callback('/:\/\/([^\/]+)/', function($matches) {
return '://' . idn_to_ascii($matches[1]);
}, $value);
}
if (preg_match($pattern, $value)) { if (preg_match($pattern, $value)) {
return true; return true;
} }
...@@ -98,10 +110,12 @@ class UrlValidator extends Validator ...@@ -98,10 +110,12 @@ class UrlValidator extends Validator
* Returns the JavaScript needed for performing client-side validation. * Returns the JavaScript needed for performing client-side validation.
* @param \yii\base\Model $object the data object being validated * @param \yii\base\Model $object the data object being validated
* @param string $attribute the name of the attribute to be validated. * @param string $attribute the name of the attribute to be validated.
* @param \yii\base\View $view the view object that is going to be used to render views or view files
* containing a model form with this validator applied.
* @return string the client-side validation script. * @return string the client-side validation script.
* @see \yii\Web\ActiveForm::enableClientValidation * @see \yii\Web\ActiveForm::enableClientValidation
*/ */
public function clientValidateAttribute($object, $attribute) public function clientValidateAttribute($object, $attribute, $view)
{ {
if (strpos($this->pattern, '{schemes}') !== false) { if (strpos($this->pattern, '{schemes}') !== false) {
$pattern = str_replace('{schemes}', '(' . implode('|', $this->validSchemes) . ')', $this->pattern); $pattern = str_replace('{schemes}', '(' . implode('|', $this->validSchemes) . ')', $this->pattern);
...@@ -115,6 +129,7 @@ class UrlValidator extends Validator ...@@ -115,6 +129,7 @@ class UrlValidator extends Validator
'{attribute}' => $object->getAttributeLabel($attribute), '{attribute}' => $object->getAttributeLabel($attribute),
'{value}' => $object->$attribute, '{value}' => $object->$attribute,
))), ))),
'enableIDN' => (boolean)$this->enableIDN,
); );
if ($this->skipOnEmpty) { if ($this->skipOnEmpty) {
$options['skipOnEmpty'] = 1; $options['skipOnEmpty'] = 1;
...@@ -123,7 +138,10 @@ class UrlValidator extends Validator ...@@ -123,7 +138,10 @@ class UrlValidator extends Validator
$options['defaultScheme'] = $this->defaultScheme; $options['defaultScheme'] = $this->defaultScheme;
} }
$view->registerAssetBundle('yii/validation');
if ($this->enableIDN) {
$view->registerAssetBundle('punycode');
}
return 'yii.validation.url(value, messages, ' . Json::encode($options) . ');'; return 'yii.validation.url(value, messages, ' . Json::encode($options) . ');';
} }
} }
...@@ -211,11 +211,13 @@ abstract class Validator extends Component ...@@ -211,11 +211,13 @@ abstract class Validator extends Component
* *
* @param \yii\base\Model $object the data object being validated * @param \yii\base\Model $object the data object being validated
* @param string $attribute the name of the attribute to be validated. * @param string $attribute the name of the attribute to be validated.
* @param \yii\base\View $view the view object that is going to be used to render views or view files
* containing a model form with this validator applied.
* @return string the client-side validation script. Null if the validator does not support * @return string the client-side validation script. Null if the validator does not support
* client-side validation. * client-side validation.
* @see \yii\web\ActiveForm::enableClientValidation * @see \yii\web\ActiveForm::enableClientValidation
*/ */
public function clientValidateAttribute($object, $attribute) public function clientValidateAttribute($object, $attribute, $view)
{ {
return null; return null;
} }
......
...@@ -138,7 +138,7 @@ class ActiveField extends Component ...@@ -138,7 +138,7 @@ class ActiveField extends Component
$validators = array(); $validators = array();
foreach ($this->model->getActiveValidators($attribute) as $validator) { foreach ($this->model->getActiveValidators($attribute) as $validator) {
/** @var \yii\validators\Validator $validator */ /** @var \yii\validators\Validator $validator */
$js = $validator->clientValidateAttribute($this->model, $attribute); $js = $validator->clientValidateAttribute($this->model, $attribute, $this->form->getView());
if ($validator->enableClientValidation && $js != '') { if ($validator->enableClientValidation && $js != '') {
$validators[] = $js; $validators[] = $js;
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment