Commit 8a5e1f4f by resurtm

Fixes #143. UrlValidator and EmailValidator IDN support.

parent 5dffc75f
......@@ -69,11 +69,26 @@
"bin": [
"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": {
"php": ">=5.3.0",
"michelf/php-markdown": "1.3",
"twig/twig": "1.12.*",
"smarty/smarty": "3.1.*",
"ezyang/htmlpurifier": "v4.5.0"
"ezyang/htmlpurifier": "v4.5.0",
"bestiejs/punycode.js": "1.2.1"
}
}
......@@ -3,9 +3,19 @@
"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"
],
"hash": "7d46ce9c4d8d5f4ecae1611ea8f0b49c",
"hash": "a8f949e337a229a4cfb41496a0071ef6",
"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",
"version": "v4.5.0",
"source": {
......
......@@ -42,4 +42,10 @@ return array(
),
'depends' => array('yii'),
),
'punycode' => array(
'sourcePath' => __DIR__ . '/vendor/bestiejs/punycode.js',
'js' => array(
'punycode.min.js',
),
),
);
......@@ -110,9 +110,18 @@ yii.validation = (function ($) {
return;
}
var valid = value.match(options.pattern) && (!options.allowName || value.match(options.fullPattern));
var valid = true;
if (!valid) {
if (options.idn) {
var regexp = /^(.*)@(.*)$/, matches = regexp.exec(value);
if (matches === null) {
valid = false;
} else {
value = punycode.toASCII(matches[1]) + '@' + punycode.toASCII(matches[2]);
}
}
if (!valid || !(value.match(options.pattern) && (!options.allowName || value.match(options.fullPattern)))) {
messages.push(options.message);
}
},
......@@ -126,7 +135,18 @@ yii.validation = (function ($) {
value = options.defaultScheme + '://' + value;
}
if (!value.match(options.pattern)) {
var valid = true;
if (options.idn) {
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);
}
},
......
......@@ -47,6 +47,12 @@ class EmailValidator extends Validator
* Defaults to 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 $idn = false;
/**
* Initializes the validator.
......@@ -81,10 +87,18 @@ class EmailValidator extends Validator
public function validateValue($value)
{
// make sure string length is limited to avoid DOS attacks
$valid = is_string($value) && strlen($value) <= 254
&& (preg_match($this->pattern, $value) || $this->allowName && preg_match($this->fullPattern, $value));
if (!is_string($value) || strlen($value) >= 255) {
return false;
}
if (($atPosition = strpos($value, '@')) === false) {
return false;
}
$domain = rtrim(substr($value, $atPosition + 1), '>');
if ($this->idn) {
$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) {
$domain = rtrim(substr($value, strpos($value, '@') + 1), '>');
if ($this->checkMX && function_exists('checkdnsrr')) {
$valid = checkdnsrr($domain, 'MX');
}
......@@ -111,6 +125,7 @@ class EmailValidator extends Validator
'{attribute}' => $object->getAttributeLabel($attribute),
'{value}' => $object->$attribute,
))),
'idn' => (boolean)$this->idn,
);
if ($this->skipOnEmpty) {
$options['skipOnEmpty'] = 1;
......
......@@ -37,6 +37,12 @@ class UrlValidator extends Validator
* contain the scheme part.
**/
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 $idn = false;
/**
......@@ -87,6 +93,12 @@ class UrlValidator extends Validator
$pattern = $this->pattern;
}
if ($this->idn) {
$value = preg_replace_callback('/:\/\/([^\/]+)/', function($matches) {
return '://' . idn_to_ascii($matches[1]);
}, $value);
}
if (preg_match($pattern, $value)) {
return true;
}
......@@ -115,6 +127,7 @@ class UrlValidator extends Validator
'{attribute}' => $object->getAttributeLabel($attribute),
'{value}' => $object->$attribute,
))),
'idn' => (boolean)$this->idn,
);
if ($this->skipOnEmpty) {
$options['skipOnEmpty'] = 1;
......@@ -126,4 +139,3 @@ class UrlValidator extends Validator
return 'yii.validation.url(value, messages, ' . Json::encode($options) . ');';
}
}
......@@ -11,6 +11,8 @@ use yii\db\ActiveRecord;
use yii\helpers\Html;
use yii\base\Model;
use yii\web\JsExpression;
use yii\validators\EmailValidator;
use yii\validators\UrlValidator;
/**
* @author Qiang Xue <qiang.xue@gmail.com>
......@@ -121,6 +123,13 @@ class ActiveField extends Component
}
$options['class'] = implode(' ', $class);
foreach ($this->model->getActiveValidators($attribute) as $validator) {
if (($validator instanceof EmailValidator || $validator instanceof UrlValidator) && $validator->idn) {
$this->form->view->registerAssetBundle('punycode');
break;
}
}
return Html::beginTag($this->tag, $options);
}
......
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