* erickskrauch-12691-url-rule-host-without-protocol:
  added test for #12697
  Used non-regex solution for trimming slashes
  Prefer strpos to substr
  Implementation of support UrlRule with relative host definition

fixes #12691
This commit is contained in:
Carsten Brandt
2017-01-31 13:43:34 +01:00
5 changed files with 171 additions and 4 deletions

View File

@ -79,6 +79,7 @@ Yii Framework 2 Change Log
- Enh #12419: Added ability to remove root tag and object tags for `yii\web\XmlResponseFormatter` (mhthnz, samdark) - Enh #12419: Added ability to remove root tag and object tags for `yii\web\XmlResponseFormatter` (mhthnz, samdark)
- Enh #12619: Added catch `Throwable` in `yii\base\ErrorHandler::handleException()`, transactions and simlar places where consistency must be kept after exception (rob006, cebe) - Enh #12619: Added catch `Throwable` in `yii\base\ErrorHandler::handleException()`, transactions and simlar places where consistency must be kept after exception (rob006, cebe)
- Enh #12659: Suggest alternatives when console command was not found (mdmunir, cebe) - Enh #12659: Suggest alternatives when console command was not found (mdmunir, cebe)
- Enh #12691: Added support for protocol-relative URLs in `yii\web\UrlRule::$pattern` (erickskrauch)
- Enh #12726: `yii\base\Application::$version` converted to `yii\base\Module::$version` virtual property, allowing to specify version as a PHP callback (klimov-paul) - Enh #12726: `yii\base\Application::$version` converted to `yii\base\Module::$version` virtual property, allowing to specify version as a PHP callback (klimov-paul)
- Enh #12732: Added `is_dir()` validation to `yii\helpers\BaseFileHelper::findFiles()` method (zalatov, silverfire) - Enh #12732: Added `is_dir()` validation to `yii\helpers\BaseFileHelper::findFiles()` method (zalatov, silverfire)
- Enh #12738: Added support for creating protocol-relative URLs in `UrlManager::createAbsoluteUrl()` and `Url` helper methods (rob006) - Enh #12738: Added support for creating protocol-relative URLs in `UrlManager::createAbsoluteUrl()` and `Url` helper methods (rob006)

View File

@ -396,6 +396,12 @@ class UrlManager extends Component
} else { } else {
return $url . $baseUrl . $anchor; return $url . $baseUrl . $anchor;
} }
} elseif (strpos($url, '//') === 0) {
if ($baseUrl !== '' && ($pos = strpos($url, '/', 2)) !== false) {
return substr($url, 0, $pos) . $baseUrl . substr($url, $pos) . $anchor;
} else {
return $url . $baseUrl . $anchor;
}
} else { } else {
return "$baseUrl/{$url}{$anchor}"; return "$baseUrl/{$url}{$anchor}";
} }
@ -475,7 +481,12 @@ class UrlManager extends Component
$params = (array) $params; $params = (array) $params;
$url = $this->createUrl($params); $url = $this->createUrl($params);
if (strpos($url, '://') === false) { if (strpos($url, '://') === false) {
$url = $this->getHostInfo() . $url; $hostInfo = $this->getHostInfo();
if (strpos($url, '//') === 0) {
$url = substr($hostInfo, 0, strpos($hostInfo, '://')) . ':' . $url;
} else {
$url = $hostInfo . $url;
}
} }
return Url::ensureScheme($url, $scheme); return Url::ensureScheme($url, $scheme);

View File

@ -178,7 +178,7 @@ class UrlRule extends Object implements UrlRuleInterface
$this->name = $this->pattern; $this->name = $this->pattern;
} }
$this->pattern = trim($this->pattern, '/'); $this->pattern = $this->trimSlashes($this->pattern);
$this->route = trim($this->route, '/'); $this->route = trim($this->route, '/');
if ($this->host !== null) { if ($this->host !== null) {
@ -195,6 +195,12 @@ class UrlRule extends Object implements UrlRuleInterface
} else { } else {
$this->host = $this->pattern; $this->host = $this->pattern;
} }
} elseif (strpos($this->pattern, '//') === 0) {
if (($pos2 = strpos($this->pattern, '/', $pos + 2)) !== false) {
$this->host = substr($this->pattern, 0, $pos2);
} else {
$this->host = $this->pattern;
}
} else { } else {
$this->pattern = '/' . $this->pattern . '/'; $this->pattern = '/' . $this->pattern . '/';
} }
@ -244,6 +250,11 @@ class UrlRule extends Object implements UrlRuleInterface
$this->_template = preg_replace('/<([\w._-]+):?([^>]+)?>/', '<$1>', $this->pattern); $this->_template = preg_replace('/<([\w._-]+):?([^>]+)?>/', '<$1>', $this->pattern);
$this->pattern = '#^' . trim(strtr($this->_template, $tr), '/') . '$#u'; $this->pattern = '#^' . trim(strtr($this->_template, $tr), '/') . '$#u';
// if host starts with relative scheme, then insert pattern to match any
if (strpos($this->host, '//') === 0) {
$this->pattern = substr_replace($this->pattern, '[\w]+://', 2, 0);
}
if (!empty($this->_routeParams)) { if (!empty($this->_routeParams)) {
$this->_routeRule = '#^' . strtr($this->route, $tr2) . '$#u'; $this->_routeRule = '#^' . strtr($this->route, $tr2) . '$#u';
} }
@ -415,7 +426,7 @@ class UrlRule extends Object implements UrlRuleInterface
} }
} }
$url = trim(strtr($this->_template, $tr), '/'); $url = $this->trimSlashes(strtr($this->_template, $tr));
if ($this->host !== null) { if ($this->host !== null) {
$pos = strpos($url, '/', 8); $pos = strpos($url, '/', 8);
if ($pos !== false) { if ($pos !== false) {
@ -467,4 +478,18 @@ class UrlRule extends Object implements UrlRuleInterface
} }
return $matches; return $matches;
} }
/**
* Trim slashes in passed string. If string begins with '//', two slashes are left as is
* in the beginning of a string.
*
* @param string $string
* @return string
*/
private function trimSlashes($string) {
if (strpos($string, '//') === 0) {
return '//' . trim($string, '/');
}
return trim($string, '/');
}
} }

View File

@ -472,7 +472,7 @@ class UrlManagerCreateUrlTest extends TestCase
[ [
'pattern' => 'post/<id>/<title>', 'pattern' => 'post/<id>/<title>',
'route' => 'post/view', 'route' => 'post/view',
'host' => 'http://<lang:en|fr>.example.com', // TODO variation of scheme https://github.com/yiisoft/yii2/issues/12691 'host' => 'http://<lang:en|fr>.example.com',
], ],
// note: baseUrl is not included in the pattern // note: baseUrl is not included in the pattern
'http://www.example.com/login' => 'site/login', 'http://www.example.com/login' => 'site/login',
@ -520,6 +520,86 @@ class UrlManagerCreateUrlTest extends TestCase
} }
/**
* Test rules that have host info in the patterns, that are protocol relative.
* @dataProvider absolutePatternsVariations
* @see https://github.com/yiisoft/yii2/issues/12691
*/
public function testProtocolRelativeAbsolutePattern($showScriptName, $prefix, $config)
{
$config['rules'] = [
[
'pattern' => 'post/<id>/<title>',
'route' => 'post/view',
'host' => '//<lang:en|fr>.example.com',
],
// note: baseUrl is not included in the pattern
'//www.example.com/login' => 'site/login',
'//app.example.com' => 'app/index',
'//app2.example.com/' => 'app2/index',
];
$manager = $this->getUrlManager($config, $showScriptName);
// first rule matches
$urlParams = ['post/view', 'id' => 1, 'title' => 'sample post', 'lang' => 'en'];
$expected = "//en.example.com$prefix/post/1/sample+post";
$this->assertEquals($expected, $manager->createUrl($urlParams));
$this->assertEquals("http:$expected", $manager->createAbsoluteUrl($urlParams));
$this->assertEquals("http:$expected", $manager->createAbsoluteUrl($urlParams, true));
$this->assertEquals("http:$expected", $manager->createAbsoluteUrl($urlParams, 'http'));
$this->assertEquals("https:$expected", $manager->createAbsoluteUrl($urlParams, 'https'));
$this->assertEquals($expected, $manager->createAbsoluteUrl($urlParams, '')); // protocol relative Url
$urlParams = ['post/view', 'id' => 1, 'title' => 'sample post', 'lang' => 'en', '#' => 'testhash'];
$expected = "//en.example.com$prefix/post/1/sample+post#testhash";
$this->assertEquals($expected, $manager->createUrl($urlParams));
$this->assertEquals("http:$expected", $manager->createAbsoluteUrl($urlParams));
// second rule matches
$urlParams = ['site/login'];
$expected = "//www.example.com$prefix/login";
$this->assertEquals($expected, $manager->createUrl($urlParams));
$this->assertEquals("http:$expected", $manager->createAbsoluteUrl($urlParams));
$this->assertEquals("http:$expected", $manager->createAbsoluteUrl($urlParams, true));
$this->assertEquals("http:$expected", $manager->createAbsoluteUrl($urlParams, 'http'));
$this->assertEquals("https:$expected", $manager->createAbsoluteUrl($urlParams, 'https'));
$this->assertEquals($expected, $manager->createAbsoluteUrl($urlParams, '')); // protocol relative Url
// third rule matches
$urlParams = ['app/index'];
$expected = "//app.example.com$prefix";
$this->assertEquals($expected, $manager->createUrl($urlParams));
$this->assertEquals("http:$expected", $manager->createAbsoluteUrl($urlParams));
$this->assertEquals("http:$expected", $manager->createAbsoluteUrl($urlParams, true));
$this->assertEquals("http:$expected", $manager->createAbsoluteUrl($urlParams, 'http'));
$this->assertEquals("https:$expected", $manager->createAbsoluteUrl($urlParams, 'https'));
$this->assertEquals($expected, $manager->createAbsoluteUrl($urlParams, '')); // protocol relative Url
// fourth rule matches
$urlParams = ['app2/index'];
$expected = "//app2.example.com$prefix";
$this->assertEquals($expected, $manager->createUrl($urlParams));
$this->assertEquals("http:$expected", $manager->createAbsoluteUrl($urlParams));
$this->assertEquals("http:$expected", $manager->createAbsoluteUrl($urlParams, true));
$this->assertEquals("http:$expected", $manager->createAbsoluteUrl($urlParams, 'http'));
$this->assertEquals("https:$expected", $manager->createAbsoluteUrl($urlParams, 'https'));
$this->assertEquals($expected, $manager->createAbsoluteUrl($urlParams, '')); // protocol relative Url
// none of the rules matches
$urlParams = ['post/index', 'page' => 1];
$this->assertEquals("$prefix/post/index?page=1", $manager->createUrl($urlParams));
$expected = "//www.example.com$prefix/post/index?page=1";
$this->assertEquals("http:$expected", $manager->createAbsoluteUrl($urlParams));
$this->assertEquals("http:$expected", $manager->createAbsoluteUrl($urlParams, true));
$this->assertEquals("http:$expected", $manager->createAbsoluteUrl($urlParams, 'http'));
$this->assertEquals("https:$expected", $manager->createAbsoluteUrl($urlParams, 'https'));
$this->assertEquals($expected, $manager->createAbsoluteUrl($urlParams, '')); // protocol relative Url
$urlParams = ['post/index', 'page' => 1, '#' => 'testhash'];
$this->assertEquals("$prefix/post/index?page=1#testhash", $manager->createUrl($urlParams));
$expected = "http://www.example.com$prefix/post/index?page=1#testhash";
$this->assertEquals($expected, $manager->createAbsoluteUrl($urlParams));
}
public function multipleHostsRulesDataProvider() public function multipleHostsRulesDataProvider()
{ {
return [ return [

View File

@ -670,6 +670,31 @@ class UrlRuleTest extends TestCase
['post/index', ['page' => 1, 'tag' => 'a', 'lang' => 'en'], 'http://en.example.com/post/a'], ['post/index', ['page' => 1, 'tag' => 'a', 'lang' => 'en'], 'http://en.example.com/post/a'],
], ],
], ],
[
'with relative host info',
[
'pattern' => 'post/<page:\d+>/<tag>',
'route' => 'post/index',
'defaults' => ['page' => 1],
'host' => '//<lang:en|fr>.example.com',
],
[
['post/index', ['page' => 1, 'tag' => 'a'], false],
['post/index', ['page' => 1, 'tag' => 'a', 'lang' => 'en'], '//en.example.com/post/a'],
],
],
[
'with relative host info in pattern',
[
'pattern' => '//<lang:en|fr>.example.com/post/<page:\d+>/<tag>',
'route' => 'post/index',
'defaults' => ['page' => 1],
],
[
['post/index', ['page' => 1, 'tag' => 'a'], false],
['post/index', ['page' => 1, 'tag' => 'a', 'lang' => 'en'], '//en.example.com/post/a'],
],
],
[ [
'with unicode', 'with unicode',
[ [
@ -1037,6 +1062,31 @@ class UrlRuleTest extends TestCase
['2', ['post/index', ['page' => 2]]], ['2', ['post/index', ['page' => 2]]],
], ],
], ],
[
'with relative host info',
[
'pattern' => 'post/<page:\d+>',
'route' => 'post/index',
'host' => '//<lang:en|fr>.example.com',
],
[
['post/1', ['post/index', ['page' => '1', 'lang' => 'en']]],
['post/a', false],
['post/1/a', false],
],
],
[
'with relative host info in pattern',
[
'pattern' => '//<lang:en|fr>.example.com/post/<page:\d+>',
'route' => 'post/index',
],
[
['post/1', ['post/index', ['page' => '1', 'lang' => 'en']]],
['post/a', false],
['post/1/a', false],
],
],
]; ];
} }