Merge pull request #10064 from SilverFire/9999-url-validator

Fixed `yii\web\UrlRule` to allow route parameter names with `-`, `_`, `.` characters
This commit is contained in:
Alexander Makarov
2015-10-31 14:15:13 +03:00
2 changed files with 58 additions and 9 deletions

View File

@@ -30,6 +30,7 @@ Yii Framework 2 Change Log
- Bug #9915: `yii\helpers\ArrayHelper::getValue()` was erroring instead of returning `null` for non-existing object properties (totaldev, samdark)
- Bug #9924: Fixed `yii.js` handleAction corrupted parameter values containing quote (") character (silverfire)
- Bug #9984: Fixed wrong captcha color in case Imagick is used (DrDeath72)
- Bug #9999: Fixed `yii\web\UrlRule` to allow route parameter names with `-`, `_`, `.`characters (silverfire)
- Bug #10029: Fixed MaskedInput not working with PJAX (martrix78, samdark)
- Bug: Fixed generation of canonical URLs for `ViewAction` pages (samdark)
- Enh #3506: Added `\yii\validators\IpValidator` to perform validation of IP addresses and subnets (SilverFire, samdark)

View File

@@ -104,6 +104,18 @@ class UrlRule extends Object implements UrlRuleInterface
* @var array list of parameters used in the route.
*/
private $_routeParams = [];
/**
* @var array list of placeholders for matching parameters names. Used in [[parseRequest()]], [[createUrl()]]
* On the rule initialization, the [[pattern]] parameters names will be replaced with placeholders.
* This array contains relations between the original parameters names and their placeholders.
* key - placeholder
* value - original name
*
* @see parseRequest()
* @see createUrl()
* @since 2.0.7
*/
private $_placeholders = [];
/**
@@ -151,7 +163,7 @@ class UrlRule extends Object implements UrlRuleInterface
$this->pattern = '/' . $this->pattern . '/';
}
if (strpos($this->route, '<') !== false && preg_match_all('/<(\w+)>/', $this->route, $matches)) {
if (strpos($this->route, '<') !== false && preg_match_all('/<([\w._-]+)>/', $this->route, $matches)) {
foreach ($matches[1] as $name) {
$this->_routeParams[$name] = "<$name>";
}
@@ -166,31 +178,34 @@ class UrlRule extends Object implements UrlRuleInterface
'(' => '\\(',
')' => '\\)',
];
$tr2 = [];
if (preg_match_all('/<(\w+):?([^>]+)?>/', $this->pattern, $matches, PREG_OFFSET_CAPTURE | PREG_SET_ORDER)) {
if (preg_match_all('/<([\w._-]+):?([^>]+)?>/', $this->pattern, $matches, PREG_OFFSET_CAPTURE | PREG_SET_ORDER)) {
foreach ($matches as $match) {
$name = $match[1][0];
$pattern = isset($match[2][0]) ? $match[2][0] : '[^\/]+';
$placeholder = 'a' . hash('crc32b', $name); // placeholder must begin with a letter
$this->_placeholders[$placeholder] = $name;
if (array_key_exists($name, $this->defaults)) {
$length = strlen($match[0][0]);
$offset = $match[0][1];
if ($offset > 1 && $this->pattern[$offset - 1] === '/' && (!isset($this->pattern[$offset + $length]) || $this->pattern[$offset + $length] === '/')) {
$tr["/<$name>"] = "(/(?P<$name>$pattern))?";
$tr["/<$name>"] = "(/(?P<$placeholder>$pattern))?";
} else {
$tr["<$name>"] = "(?P<$name>$pattern)?";
$tr["<$name>"] = "(?P<$placeholder>$pattern)?";
}
} else {
$tr["<$name>"] = "(?P<$name>$pattern)";
$tr["<$name>"] = "(?P<$placeholder>$pattern)";
}
if (isset($this->_routeParams[$name])) {
$tr2["<$name>"] = "(?P<$name>$pattern)";
$tr2["<$name>"] = "(?P<$placeholder>$pattern)";
} else {
$this->_paramRules[$name] = $pattern === '[^\/]+' ? '' : "#^$pattern$#u";
}
}
}
$this->_template = preg_replace('/<(\w+):?([^>]+)?>/', '<$1>', $this->pattern);
$this->_template = preg_replace('/<([\w._-]+):?([^>]+)?>/', '<$1>', $this->pattern);
$this->pattern = '#^' . trim(strtr($this->_template, $tr), '/') . '$#u';
if (!empty($this->_routeParams)) {
@@ -216,7 +231,7 @@ class UrlRule extends Object implements UrlRuleInterface
}
$pathInfo = $request->getPathInfo();
$suffix = (string) ($this->suffix === null ? $manager->suffix : $this->suffix);
$suffix = (string)($this->suffix === null ? $manager->suffix : $this->suffix);
if ($suffix !== '' && $pathInfo !== '') {
$n = strlen($suffix);
if (substr_compare($pathInfo, $suffix, -$n, $n) === 0) {
@@ -237,6 +252,8 @@ class UrlRule extends Object implements UrlRuleInterface
if (!preg_match($this->pattern, $pathInfo, $matches)) {
return false;
}
$matches = $this->substitutePlaceholderNames($matches);
foreach ($this->defaults as $name => $value) {
if (!isset($matches[$name]) || $matches[$name] === '') {
$matches[$name] = $value;
@@ -281,6 +298,7 @@ class UrlRule extends Object implements UrlRuleInterface
// match the route part first
if ($route !== $this->route) {
if ($this->_routeRule !== null && preg_match($this->_routeRule, $route, $matches)) {
$matches = $this->substitutePlaceholderNames($matches);
foreach ($this->_routeParams as $name => $token) {
if (isset($this->defaults[$name]) && strcmp($this->defaults[$name], $matches[$name]) === 0) {
$tr[$token] = '';
@@ -352,4 +370,34 @@ class UrlRule extends Object implements UrlRuleInterface
{
return $this->_paramRules;
}
}
/**
* Iterates over [[_placeholders]] and checks whether each placeholder exists as a key in $matches array.
* When found - replaces this placeholder key with a appropriate name of matching parameter.
* Used in [[parseRequest()]], [[createUrl()]].
*
* @param array $matches result of `preg_match()` call
* @return array input array with replaced placeholder keys
* @see _placeholders
* @since 2.0.7
*/
private function substitutePlaceholderNames (array $matches) {
foreach ($this->_placeholders as $placeholder => $name) {
if (isset($matches[$placeholder])) {
$matches[$name] = $matches[$placeholder];
unset($matches[$placeholder]);
}
}
return $matches;
}
/**
* Returns list of placeholders and original names for matching parameters.
* @return array
* @since 2.0.7
* @see _placeholders
*/
protected function getPlaceholders() {
return $this->_placeholders;
}
}