From 6465a6987a8bfedc2c8d983eef7e30bbb4bebdce Mon Sep 17 00:00:00 2001 From: Victor Berchet Date: Tue, 10 Apr 2012 14:42:59 +0200 Subject: [PATCH] [Routing] Fixes to handle spaces in route pattern - The route compiler does not add extra space or line-feed, - The generated regex does not use the 'x' modified any more, - The PHP and apache matchers do not need to strip any chars (vs space and line feed before), - The space characters are escaped according to the apache format --- .../Matcher/Dumper/ApacheMatcherDumper.php | 47 ++++++++++++- .../Matcher/Dumper/PhpMatcherDumper.php | 4 +- .../Component/Routing/RouteCompiler.php | 66 ++++++++++++------- .../Fixtures/dumper/url_matcher1.apache | 4 ++ .../Routing/Fixtures/dumper/url_matcher1.php | 33 ++++++---- .../Routing/Fixtures/dumper/url_matcher2.php | 33 ++++++---- .../Dumper/ApacheMatcherDumperTest.php | 38 +++++++++-- .../Matcher/Dumper/PhpMatcherDumperTest.php | 4 ++ .../Component/Routing/RouteCompilerTest.php | 18 ++--- 9 files changed, 177 insertions(+), 70 deletions(-) diff --git a/src/Symfony/Component/Routing/Matcher/Dumper/ApacheMatcherDumper.php b/src/Symfony/Component/Routing/Matcher/Dumper/ApacheMatcherDumper.php index 0b3836a3a2..8be0622dc6 100644 --- a/src/Symfony/Component/Routing/Matcher/Dumper/ApacheMatcherDumper.php +++ b/src/Symfony/Component/Routing/Matcher/Dumper/ApacheMatcherDumper.php @@ -31,6 +31,8 @@ class ApacheMatcherDumper extends MatcherDumper * @param array $options An array of options * * @return string A string to be used as Apache rewrite rules + * + * @throws \LogicException When the route regex is invalid */ public function dump(array $options = array()) { @@ -39,6 +41,8 @@ class ApacheMatcherDumper extends MatcherDumper 'base_uri' => '', ), $options); + $options['script_name'] = self::escape($options['script_name'], ' ', '\\'); + $rules = array("# skip \"real\" requests\nRewriteCond %{REQUEST_FILENAME} -f\nRewriteRule .* - [QSA,L]"); $methodVars = array(); @@ -46,8 +50,14 @@ class ApacheMatcherDumper extends MatcherDumper $compiledRoute = $route->compile(); // prepare the apache regex - $regex = preg_replace('/\?P<.+?>/', '', substr(str_replace(array("\n", ' '), '', $compiledRoute->getRegex()), 1, -3)); - $regex = '^'.preg_quote($options['base_uri']).substr($regex, 1); + $regex = $compiledRoute->getRegex(); + $delimiter = $regex[0]; + $regexPatternEnd = strrpos($regex, $delimiter); + if (strlen($regex) < 2 || 0 === $regexPatternEnd) { + throw new \LogicException('The "%s" route regex "%s" is invalid', $name, $regex); + } + $regex = preg_replace('/\?P<.+?>/', '', substr($regex, 1, $regexPatternEnd - 1)); + $regex = '^'.self::escape(preg_quote($options['base_uri']).substr($regex, 1), ' ', '\\'); $hasTrailingSlash = '/$' == substr($regex, -2) && '^/$' != $regex; @@ -56,7 +66,6 @@ class ApacheMatcherDumper extends MatcherDumper $variables[] = 'E=_ROUTING_'.$variable.':%'.($i + 1); } foreach ($route->getDefaults() as $key => $value) { - // todo: a more legit way to escape the value? $variables[] = 'E=_ROUTING_'.$key.':'.strtr($value, array( ':' => '\\:', '=' => '\\=', @@ -112,4 +121,36 @@ class ApacheMatcherDumper extends MatcherDumper return implode("\n\n", $rules)."\n"; } + + /** + * Escapes a string. + * + * @param string $string The string to be escaped + * @param string $char The character to be escaped + * @param string $with The character to be used for escaping + * + * @return string The escaped string + */ + static private function escape($string, $char, $with) + { + $escaped = false; + $output = ''; + foreach(str_split($string) as $symbol) { + if ($escaped) { + $output .= $symbol; + $escaped = false; + continue; + } + if ($symbol === $char) { + $output .= $with.$char; + continue; + } + if ($symbol === $with) { + $escaped = true; + } + $output .= $symbol; + } + + return $output; + } } diff --git a/src/Symfony/Component/Routing/Matcher/Dumper/PhpMatcherDumper.php b/src/Symfony/Component/Routing/Matcher/Dumper/PhpMatcherDumper.php index 6ec216d324..0b0e94433f 100644 --- a/src/Symfony/Component/Routing/Matcher/Dumper/PhpMatcherDumper.php +++ b/src/Symfony/Component/Routing/Matcher/Dumper/PhpMatcherDumper.php @@ -148,7 +148,7 @@ EOF; $conditions = array(); $hasTrailingSlash = false; $matches = false; - if (!count($compiledRoute->getVariables()) && false !== preg_match('#^(.)\^(?P.*?)\$\1#', str_replace(array("\n", ' '), '', $compiledRoute->getRegex()), $m)) { + if (!count($compiledRoute->getVariables()) && false !== preg_match('#^(.)\^(?P.*?)\$\1#', $compiledRoute->getRegex(), $m)) { if ($supportsRedirections && substr($m['url'], -1) === '/') { $conditions[] = sprintf("rtrim(\$pathinfo, '/') === %s", var_export(rtrim(str_replace('\\', '', $m['url']), '/'), true)); $hasTrailingSlash = true; @@ -160,7 +160,7 @@ EOF; $conditions[] = sprintf("0 === strpos(\$pathinfo, %s)", var_export($compiledRoute->getStaticPrefix(), true)); } - $regex = str_replace(array("\n", ' '), '', $compiledRoute->getRegex()); + $regex = $compiledRoute->getRegex(); if ($supportsRedirections && $pos = strpos($regex, '/$')) { $regex = substr($regex, 0, $pos).'/?$'.substr($regex, $pos + 2); $hasTrailingSlash = true; diff --git a/src/Symfony/Component/Routing/RouteCompiler.php b/src/Symfony/Component/Routing/RouteCompiler.php index 5cebd224f7..e816de4954 100644 --- a/src/Symfony/Component/Routing/RouteCompiler.php +++ b/src/Symfony/Component/Routing/RouteCompiler.php @@ -66,7 +66,8 @@ class RouteCompiler implements RouteCompilerInterface // find the first optional token $firstOptional = INF; for ($i = count($tokens) - 1; $i >= 0; $i--) { - if ('variable' === $tokens[$i][0] && $route->hasDefault($tokens[$i][3])) { + $token = $tokens[$i]; + if ('variable' === $token[0] && $route->hasDefault($token[3])) { $firstOptional = $i; } else { break; @@ -74,36 +75,53 @@ class RouteCompiler implements RouteCompilerInterface } // compute the matching regexp - $regex = ''; - $indent = 1; - if (1 === count($tokens) && 0 === $firstOptional) { - $token = $tokens[0]; - ++$indent; - $regex .= str_repeat(' ', $indent * 4).sprintf("%s(?:\n", preg_quote($token[1], '#')); - $regex .= str_repeat(' ', $indent * 4).sprintf("(?P<%s>%s)\n", $token[3], $token[2]); - } else { - foreach ($tokens as $i => $token) { - if ('text' === $token[0]) { - $regex .= str_repeat(' ', $indent * 4).preg_quote($token[1], '#')."\n"; - } else { - if ($i >= $firstOptional) { - $regex .= str_repeat(' ', $indent * 4)."(?:\n"; - ++$indent; - } - $regex .= str_repeat(' ', $indent * 4).sprintf("%s(?P<%s>%s)\n", preg_quote($token[1], '#'), $token[3], $token[2]); - } - } - } - while (--$indent) { - $regex .= str_repeat(' ', $indent * 4).")?\n"; + $regexp = ''; + for ($i = 0, $nbToken = count($tokens); $i < $nbToken; $i++) { + $regexp .= $this->computeRegexp($tokens, $i, $firstOptional); } return new CompiledRoute( $route, 'text' === $tokens[0][0] ? $tokens[0][1] : '', - sprintf("#^\n%s$#xs", $regex), + sprintf("#^%s$#s", $regexp), array_reverse($tokens), $variables ); } + + /** + * Computes the regexp used to match the token. + * + * @param array $tokens The route tokens + * @param integer $index The index of the current token + * @param integer $firstOptional The index of the first optional token + * + * @return string The regexp + */ + private function computeRegexp(array $tokens, $index, $firstOptional) + { + $token = $tokens[$index]; + if('text' === $token[0]) { + // Text tokens + return preg_quote($token[1], '#'); + } else { + // Variable tokens + if (0 === $index && 0 === $firstOptional && 1 == count($tokens)) { + // When the only token is an optional variable token, the separator is required + return sprintf('%s(?P<%s>%s)?', preg_quote($token[1], '#'), $token[3], $token[2]); + } else { + $nbTokens = count($tokens); + $regexp = sprintf('%s(?P<%s>%s)', preg_quote($token[1], '#'), $token[3], $token[2]); + if ($index >= $firstOptional) { + // Enclose each optional tokens in a subpattern to make it optional + $regexp = "(?:$regexp"; + if ($nbTokens - 1 == $index) { + // Close the optional subpatterns + $regexp .= str_repeat(")?", $nbTokens - $firstOptional); + } + } + return $regexp; + } + } + } } diff --git a/tests/Symfony/Tests/Component/Routing/Fixtures/dumper/url_matcher1.apache b/tests/Symfony/Tests/Component/Routing/Fixtures/dumper/url_matcher1.apache index 9f4a7b49f4..9f0332f677 100644 --- a/tests/Symfony/Tests/Component/Routing/Fixtures/dumper/url_matcher1.apache +++ b/tests/Symfony/Tests/Component/Routing/Fixtures/dumper/url_matcher1.apache @@ -53,6 +53,10 @@ RewriteRule .* app.php [QSA,L,E=_ROUTING__route:baz5,E=_ROUTING_foo:%1] RewriteCond %{REQUEST_URI} ^/test/baz$ RewriteRule .* app.php [QSA,L,E=_ROUTING__route:baz6,E=_ROUTING_foo:bar\ baz] +# baz7 +RewriteCond %{REQUEST_URI} ^/te\ st/baz$ +RewriteRule .* app.php [QSA,L,E=_ROUTING__route:baz7] + # 405 Method Not Allowed RewriteCond %{_ROUTING__allow_GET} !-z [OR] RewriteCond %{_ROUTING__allow_HEAD} !-z [OR] diff --git a/tests/Symfony/Tests/Component/Routing/Fixtures/dumper/url_matcher1.php b/tests/Symfony/Tests/Component/Routing/Fixtures/dumper/url_matcher1.php index 7932944a34..d406be6855 100644 --- a/tests/Symfony/Tests/Component/Routing/Fixtures/dumper/url_matcher1.php +++ b/tests/Symfony/Tests/Component/Routing/Fixtures/dumper/url_matcher1.php @@ -26,12 +26,12 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Matcher\UrlMatcher $pathinfo = urldecode($pathinfo); // foo - if (0 === strpos($pathinfo, '/foo') && preg_match('#^/foo/(?Pbaz|symfony)$#xs', $pathinfo, $matches)) { + if (0 === strpos($pathinfo, '/foo') && preg_match('#^/foo/(?Pbaz|symfony)$#s', $pathinfo, $matches)) { return array_merge($this->mergeDefaults($matches, array ( 'def' => 'test',)), array('_route' => 'foo')); } // bar - if (0 === strpos($pathinfo, '/bar') && preg_match('#^/bar/(?P[^/]+?)$#xs', $pathinfo, $matches)) { + if (0 === strpos($pathinfo, '/bar') && preg_match('#^/bar/(?P[^/]+?)$#s', $pathinfo, $matches)) { if (!in_array($this->context->getMethod(), array('GET', 'HEAD'))) { $allow = array_merge($allow, array('GET', 'HEAD')); goto not_bar; @@ -42,7 +42,7 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Matcher\UrlMatcher not_bar: // barhead - if (0 === strpos($pathinfo, '/barhead') && preg_match('#^/barhead/(?P[^/]+?)$#xs', $pathinfo, $matches)) { + if (0 === strpos($pathinfo, '/barhead') && preg_match('#^/barhead/(?P[^/]+?)$#s', $pathinfo, $matches)) { if (!in_array($this->context->getMethod(), array('GET', 'HEAD'))) { $allow = array_merge($allow, array('GET', 'HEAD')); goto not_barhead; @@ -68,13 +68,13 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Matcher\UrlMatcher } // baz4 - if (0 === strpos($pathinfo, '/test') && preg_match('#^/test/(?P[^/]+?)/$#xs', $pathinfo, $matches)) { + if (0 === strpos($pathinfo, '/test') && preg_match('#^/test/(?P[^/]+?)/$#s', $pathinfo, $matches)) { $matches['_route'] = 'baz4'; return $matches; } // baz5 - if (0 === strpos($pathinfo, '/test') && preg_match('#^/test/(?P[^/]+?)/$#xs', $pathinfo, $matches)) { + if (0 === strpos($pathinfo, '/test') && preg_match('#^/test/(?P[^/]+?)/$#s', $pathinfo, $matches)) { if ($this->context->getMethod() != 'POST') { $allow[] = 'POST'; goto not_baz5; @@ -85,7 +85,7 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Matcher\UrlMatcher not_baz5: // baz.baz6 - if (0 === strpos($pathinfo, '/test') && preg_match('#^/test/(?P[^/]+?)/$#xs', $pathinfo, $matches)) { + if (0 === strpos($pathinfo, '/test') && preg_match('#^/test/(?P[^/]+?)/$#s', $pathinfo, $matches)) { if ($this->context->getMethod() != 'PUT') { $allow[] = 'PUT'; goto not_bazbaz6; @@ -101,33 +101,38 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Matcher\UrlMatcher } // quoter - if (preg_match('#^/(?P[\']+)$#xs', $pathinfo, $matches)) { + if (preg_match('#^/(?P[\']+)$#s', $pathinfo, $matches)) { $matches['_route'] = 'quoter'; return $matches; } + // space + if ($pathinfo === '/spa ce') { + return array('_route' => 'space'); + } + if (0 === strpos($pathinfo, '/a')) { if (0 === strpos($pathinfo, '/a/b\'b')) { // foo1 - if (preg_match('#^/a/b\'b/(?P[^/]+?)$#xs', $pathinfo, $matches)) { + if (preg_match('#^/a/b\'b/(?P[^/]+?)$#s', $pathinfo, $matches)) { $matches['_route'] = 'foo1'; return $matches; } // bar1 - if (preg_match('#^/a/b\'b/(?P[^/]+?)$#xs', $pathinfo, $matches)) { + if (preg_match('#^/a/b\'b/(?P[^/]+?)$#s', $pathinfo, $matches)) { $matches['_route'] = 'bar1'; return $matches; } // foo2 - if (preg_match('#^/a/b\'b/(?P[^/]+?)$#xs', $pathinfo, $matches)) { + if (preg_match('#^/a/b\'b/(?P[^/]+?)$#s', $pathinfo, $matches)) { $matches['_route'] = 'foo2'; return $matches; } // bar2 - if (preg_match('#^/a/b\'b/(?P[^/]+?)$#xs', $pathinfo, $matches)) { + if (preg_match('#^/a/b\'b/(?P[^/]+?)$#s', $pathinfo, $matches)) { $matches['_route'] = 'bar2'; return $matches; } @@ -145,7 +150,7 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Matcher\UrlMatcher } // foo4 - if (preg_match('#^/aba/(?P[^/]+?)$#xs', $pathinfo, $matches)) { + if (preg_match('#^/aba/(?P[^/]+?)$#s', $pathinfo, $matches)) { $matches['_route'] = 'foo4'; return $matches; } @@ -153,13 +158,13 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Matcher\UrlMatcher } // foo3 - if (preg_match('#^/(?P<_locale>[^/]+?)/b/(?P[^/]+?)$#xs', $pathinfo, $matches)) { + if (preg_match('#^/(?P<_locale>[^/]+?)/b/(?P[^/]+?)$#s', $pathinfo, $matches)) { $matches['_route'] = 'foo3'; return $matches; } // bar3 - if (preg_match('#^/(?P<_locale>[^/]+?)/b/(?P[^/]+?)$#xs', $pathinfo, $matches)) { + if (preg_match('#^/(?P<_locale>[^/]+?)/b/(?P[^/]+?)$#s', $pathinfo, $matches)) { $matches['_route'] = 'bar3'; return $matches; } diff --git a/tests/Symfony/Tests/Component/Routing/Fixtures/dumper/url_matcher2.php b/tests/Symfony/Tests/Component/Routing/Fixtures/dumper/url_matcher2.php index d0f1e31381..451aad17cb 100644 --- a/tests/Symfony/Tests/Component/Routing/Fixtures/dumper/url_matcher2.php +++ b/tests/Symfony/Tests/Component/Routing/Fixtures/dumper/url_matcher2.php @@ -26,12 +26,12 @@ class ProjectUrlMatcher extends Symfony\Tests\Component\Routing\Fixtures\Redirec $pathinfo = urldecode($pathinfo); // foo - if (0 === strpos($pathinfo, '/foo') && preg_match('#^/foo/(?Pbaz|symfony)$#xs', $pathinfo, $matches)) { + if (0 === strpos($pathinfo, '/foo') && preg_match('#^/foo/(?Pbaz|symfony)$#s', $pathinfo, $matches)) { return array_merge($this->mergeDefaults($matches, array ( 'def' => 'test',)), array('_route' => 'foo')); } // bar - if (0 === strpos($pathinfo, '/bar') && preg_match('#^/bar/(?P[^/]+?)$#xs', $pathinfo, $matches)) { + if (0 === strpos($pathinfo, '/bar') && preg_match('#^/bar/(?P[^/]+?)$#s', $pathinfo, $matches)) { if (!in_array($this->context->getMethod(), array('GET', 'HEAD'))) { $allow = array_merge($allow, array('GET', 'HEAD')); goto not_bar; @@ -42,7 +42,7 @@ class ProjectUrlMatcher extends Symfony\Tests\Component\Routing\Fixtures\Redirec not_bar: // barhead - if (0 === strpos($pathinfo, '/barhead') && preg_match('#^/barhead/(?P[^/]+?)$#xs', $pathinfo, $matches)) { + if (0 === strpos($pathinfo, '/barhead') && preg_match('#^/barhead/(?P[^/]+?)$#s', $pathinfo, $matches)) { if (!in_array($this->context->getMethod(), array('GET', 'HEAD'))) { $allow = array_merge($allow, array('GET', 'HEAD')); goto not_barhead; @@ -71,7 +71,7 @@ class ProjectUrlMatcher extends Symfony\Tests\Component\Routing\Fixtures\Redirec } // baz4 - if (0 === strpos($pathinfo, '/test') && preg_match('#^/test/(?P[^/]+?)/?$#xs', $pathinfo, $matches)) { + if (0 === strpos($pathinfo, '/test') && preg_match('#^/test/(?P[^/]+?)/?$#s', $pathinfo, $matches)) { if (substr($pathinfo, -1) !== '/') { return $this->redirect($pathinfo.'/', 'baz4'); } @@ -80,7 +80,7 @@ class ProjectUrlMatcher extends Symfony\Tests\Component\Routing\Fixtures\Redirec } // baz5 - if (0 === strpos($pathinfo, '/test') && preg_match('#^/test/(?P[^/]+?)/?$#xs', $pathinfo, $matches)) { + if (0 === strpos($pathinfo, '/test') && preg_match('#^/test/(?P[^/]+?)/?$#s', $pathinfo, $matches)) { if ($this->context->getMethod() != 'POST') { $allow[] = 'POST'; goto not_baz5; @@ -94,7 +94,7 @@ class ProjectUrlMatcher extends Symfony\Tests\Component\Routing\Fixtures\Redirec not_baz5: // baz.baz6 - if (0 === strpos($pathinfo, '/test') && preg_match('#^/test/(?P[^/]+?)/?$#xs', $pathinfo, $matches)) { + if (0 === strpos($pathinfo, '/test') && preg_match('#^/test/(?P[^/]+?)/?$#s', $pathinfo, $matches)) { if ($this->context->getMethod() != 'PUT') { $allow[] = 'PUT'; goto not_bazbaz6; @@ -113,33 +113,38 @@ class ProjectUrlMatcher extends Symfony\Tests\Component\Routing\Fixtures\Redirec } // quoter - if (preg_match('#^/(?P[\']+)$#xs', $pathinfo, $matches)) { + if (preg_match('#^/(?P[\']+)$#s', $pathinfo, $matches)) { $matches['_route'] = 'quoter'; return $matches; } + // space + if ($pathinfo === '/spa ce') { + return array('_route' => 'space'); + } + if (0 === strpos($pathinfo, '/a')) { if (0 === strpos($pathinfo, '/a/b\'b')) { // foo1 - if (preg_match('#^/a/b\'b/(?P[^/]+?)$#xs', $pathinfo, $matches)) { + if (preg_match('#^/a/b\'b/(?P[^/]+?)$#s', $pathinfo, $matches)) { $matches['_route'] = 'foo1'; return $matches; } // bar1 - if (preg_match('#^/a/b\'b/(?P[^/]+?)$#xs', $pathinfo, $matches)) { + if (preg_match('#^/a/b\'b/(?P[^/]+?)$#s', $pathinfo, $matches)) { $matches['_route'] = 'bar1'; return $matches; } // foo2 - if (preg_match('#^/a/b\'b/(?P[^/]+?)$#xs', $pathinfo, $matches)) { + if (preg_match('#^/a/b\'b/(?P[^/]+?)$#s', $pathinfo, $matches)) { $matches['_route'] = 'foo2'; return $matches; } // bar2 - if (preg_match('#^/a/b\'b/(?P[^/]+?)$#xs', $pathinfo, $matches)) { + if (preg_match('#^/a/b\'b/(?P[^/]+?)$#s', $pathinfo, $matches)) { $matches['_route'] = 'bar2'; return $matches; } @@ -157,7 +162,7 @@ class ProjectUrlMatcher extends Symfony\Tests\Component\Routing\Fixtures\Redirec } // foo4 - if (preg_match('#^/aba/(?P[^/]+?)$#xs', $pathinfo, $matches)) { + if (preg_match('#^/aba/(?P[^/]+?)$#s', $pathinfo, $matches)) { $matches['_route'] = 'foo4'; return $matches; } @@ -165,13 +170,13 @@ class ProjectUrlMatcher extends Symfony\Tests\Component\Routing\Fixtures\Redirec } // foo3 - if (preg_match('#^/(?P<_locale>[^/]+?)/b/(?P[^/]+?)$#xs', $pathinfo, $matches)) { + if (preg_match('#^/(?P<_locale>[^/]+?)/b/(?P[^/]+?)$#s', $pathinfo, $matches)) { $matches['_route'] = 'foo3'; return $matches; } // bar3 - if (preg_match('#^/(?P<_locale>[^/]+?)/b/(?P[^/]+?)$#xs', $pathinfo, $matches)) { + if (preg_match('#^/(?P<_locale>[^/]+?)/b/(?P[^/]+?)$#s', $pathinfo, $matches)) { $matches['_route'] = 'bar3'; return $matches; } diff --git a/tests/Symfony/Tests/Component/Routing/Matcher/Dumper/ApacheMatcherDumperTest.php b/tests/Symfony/Tests/Component/Routing/Matcher/Dumper/ApacheMatcherDumperTest.php index 34be50c96d..97d78f9c77 100644 --- a/tests/Symfony/Tests/Component/Routing/Matcher/Dumper/ApacheMatcherDumperTest.php +++ b/tests/Symfony/Tests/Component/Routing/Matcher/Dumper/ApacheMatcherDumperTest.php @@ -25,6 +25,34 @@ class ApacheMatcherDumperTest extends \PHPUnit_Framework_TestCase } public function testDump() + { + $dumper = new ApacheMatcherDumper($this->getRouteCollection()); + + $this->assertStringEqualsFile(self::$fixturesPath.'/dumper/url_matcher1.apache', $dumper->dump(), '->dump() dumps basic routes to the correct apache format.'); + } + + /** + * @dataProvider provideEscapeFixtures + */ + public function testEscape($src, $dest, $char, $with, $message) + { + $r = new \ReflectionMethod(new ApacheMatcherDumper($this->getRouteCollection()), 'escape'); + $r->setAccessible(true); + $this->assertEquals($dest, $r->invoke(null, $src, $char, $with), $message); + } + + public function provideEscapeFixtures() + { + return array( + array('foo', 'foo', ' ', '-', 'Preserve string that should not be escaped'), + array('fo-o', 'fo-o', ' ', '-', 'Preserve string that should not be escaped'), + array('fo o', 'fo- o', ' ', '-', 'Escape special characters'), + array('fo-- o', 'fo--- o', ' ', '-', 'Escape special characters'), + array('fo- o', 'fo- o', ' ', '-', 'Do not escape already escaped string'), + ); + } + + private function getRouteCollection() { $collection = new RouteCollection(); @@ -73,9 +101,11 @@ class ApacheMatcherDumperTest extends \PHPUnit_Framework_TestCase '/test/baz', array('foo' => 'bar baz') )); + // space in path + $collection->add('baz7', new Route( + '/te st/baz' + )); - $dumper = new ApacheMatcherDumper($collection); - - $this->assertStringEqualsFile(self::$fixturesPath.'/dumper/url_matcher1.apache', $dumper->dump(), '->dump() dumps basic routes to the correct apache format.'); + return $collection; } -} +} \ No newline at end of file diff --git a/tests/Symfony/Tests/Component/Routing/Matcher/Dumper/PhpMatcherDumperTest.php b/tests/Symfony/Tests/Component/Routing/Matcher/Dumper/PhpMatcherDumperTest.php index ec8b6688d6..ee91b7324c 100644 --- a/tests/Symfony/Tests/Component/Routing/Matcher/Dumper/PhpMatcherDumperTest.php +++ b/tests/Symfony/Tests/Component/Routing/Matcher/Dumper/PhpMatcherDumperTest.php @@ -123,6 +123,10 @@ class PhpMatcherDumperTest extends \PHPUnit_Framework_TestCase array(), array('quoter' => '[\']+') )); + // space in pattern + $collection->add('space', new Route( + '/spa ce' + )); // prefixes $collection1 = new RouteCollection(); diff --git a/tests/Symfony/Tests/Component/Routing/RouteCompilerTest.php b/tests/Symfony/Tests/Component/Routing/RouteCompilerTest.php index 08120e7848..81c6a93a10 100644 --- a/tests/Symfony/Tests/Component/Routing/RouteCompilerTest.php +++ b/tests/Symfony/Tests/Component/Routing/RouteCompilerTest.php @@ -25,7 +25,7 @@ class RouteCompilerTest extends \PHPUnit_Framework_TestCase $compiled = $route->compile(); $this->assertEquals($prefix, $compiled->getStaticPrefix(), $name.' (static prefix)'); - $this->assertEquals($regex, str_replace(array("\n", ' '), '', $compiled->getRegex()), $name.' (regex)'); + $this->assertEquals($regex, $compiled->getRegex(), $name.' (regex)'); $this->assertEquals($variables, $compiled->getVariables(), $name.' (variables)'); $this->assertEquals($tokens, $compiled->getTokens(), $name.' (tokens)'); } @@ -36,14 +36,14 @@ class RouteCompilerTest extends \PHPUnit_Framework_TestCase array( 'Static route', array('/foo'), - '/foo', '#^/foo$#xs', array(), array( + '/foo', '#^/foo$#s', array(), array( array('text', '/foo'), )), array( 'Route with a variable', array('/foo/{bar}'), - '/foo', '#^/foo/(?P[^/]+?)$#xs', array('bar'), array( + '/foo', '#^/foo/(?P[^/]+?)$#s', array('bar'), array( array('variable', '/', '[^/]+?', 'bar'), array('text', '/foo'), )), @@ -51,7 +51,7 @@ class RouteCompilerTest extends \PHPUnit_Framework_TestCase array( 'Route with a variable that has a default value', array('/foo/{bar}', array('bar' => 'bar')), - '/foo', '#^/foo(?:/(?P[^/]+?))?$#xs', array('bar'), array( + '/foo', '#^/foo(?:/(?P[^/]+?))?$#s', array('bar'), array( array('variable', '/', '[^/]+?', 'bar'), array('text', '/foo'), )), @@ -59,7 +59,7 @@ class RouteCompilerTest extends \PHPUnit_Framework_TestCase array( 'Route with several variables', array('/foo/{bar}/{foobar}'), - '/foo', '#^/foo/(?P[^/]+?)/(?P[^/]+?)$#xs', array('bar', 'foobar'), array( + '/foo', '#^/foo/(?P[^/]+?)/(?P[^/]+?)$#s', array('bar', 'foobar'), array( array('variable', '/', '[^/]+?', 'foobar'), array('variable', '/', '[^/]+?', 'bar'), array('text', '/foo'), @@ -68,7 +68,7 @@ class RouteCompilerTest extends \PHPUnit_Framework_TestCase array( 'Route with several variables that have default values', array('/foo/{bar}/{foobar}', array('bar' => 'bar', 'foobar' => '')), - '/foo', '#^/foo(?:/(?P[^/]+?)(?:/(?P[^/]+?))?)?$#xs', array('bar', 'foobar'), array( + '/foo', '#^/foo(?:/(?P[^/]+?)(?:/(?P[^/]+?))?)?$#s', array('bar', 'foobar'), array( array('variable', '/', '[^/]+?', 'foobar'), array('variable', '/', '[^/]+?', 'bar'), array('text', '/foo'), @@ -77,7 +77,7 @@ class RouteCompilerTest extends \PHPUnit_Framework_TestCase array( 'Route with several variables but some of them have no default values', array('/foo/{bar}/{foobar}', array('bar' => 'bar')), - '/foo', '#^/foo/(?P[^/]+?)/(?P[^/]+?)$#xs', array('bar', 'foobar'), array( + '/foo', '#^/foo/(?P[^/]+?)/(?P[^/]+?)$#s', array('bar', 'foobar'), array( array('variable', '/', '[^/]+?', 'foobar'), array('variable', '/', '[^/]+?', 'bar'), array('text', '/foo'), @@ -86,14 +86,14 @@ class RouteCompilerTest extends \PHPUnit_Framework_TestCase array( 'Route with an optional variable as the first segment', array('/{bar}', array('bar' => 'bar')), - '', '#^/(?:(?P[^/]+?))?$#xs', array('bar'), array( + '', '#^/(?P[^/]+?)?$#s', array('bar'), array( array('variable', '/', '[^/]+?', 'bar'), )), array( 'Route with an optional variable as the first segment with requirements', array('/{bar}', array('bar' => 'bar'), array('bar' => '(foo|bar)')), - '', '#^/(?:(?P(foo|bar)))?$#xs', array('bar'), array( + '', '#^/(?P(foo|bar))?$#s', array('bar'), array( array('variable', '/', '(foo|bar)', 'bar'), )), );