From 957cc551b620388b4362161b477a3689eec7d824 Mon Sep 17 00:00:00 2001 From: Tobias Schultze Date: Wed, 13 Jun 2012 01:10:11 +0200 Subject: [PATCH 1/2] [Routing] add escaping syntax for placeholders --- .../Component/Routing/RouteCompiler.php | 38 ++++++++++++++++--- .../Routing/Tests/RouteCompilerTest.php | 25 ++++++++++++ 2 files changed, 58 insertions(+), 5 deletions(-) diff --git a/src/Symfony/Component/Routing/RouteCompiler.php b/src/Symfony/Component/Routing/RouteCompiler.php index 03d3e38644ef0..c65f975426812 100644 --- a/src/Symfony/Component/Routing/RouteCompiler.php +++ b/src/Symfony/Component/Routing/RouteCompiler.php @@ -54,6 +54,13 @@ public function compile(Route $route) $precedingChar = strlen($precedingText) > 0 ? substr($precedingText, -1) : ''; $isSeparator = '' !== $precedingChar && false !== strpos(static::SEPARATORS, $precedingChar); + if ('\\' === $precedingChar) { + // When the variable placeholder is excaped with "\", e.g. "static\{static}", treat it as static text (without the escape char). + // This allows "{static}" to be used in the route without being considered as variable. + $this->addTextToken($tokens, substr($precedingText, 0, -1) . $match[0][0]); + continue; + } + if (is_numeric($varName)) { throw new \DomainException(sprintf('Variable name "%s" cannot be numeric in route pattern "%s". Please use a different name.', $varName, $pattern)); } @@ -62,9 +69,9 @@ public function compile(Route $route) } if ($isSeparator && strlen($precedingText) > 1) { - $tokens[] = array('text', substr($precedingText, 0, -1)); + $this->addTextToken($tokens, substr($precedingText, 0, -1)); } elseif (!$isSeparator && strlen($precedingText) > 0) { - $tokens[] = array('text', $precedingText); + $this->addTextToken($tokens, $precedingText); } $regexp = $route->getRequirement($varName); @@ -94,7 +101,7 @@ public function compile(Route $route) } if ($pos < strlen($pattern)) { - $tokens[] = array('text', substr($pattern, $pos)); + $this->addTextToken($tokens, substr($pattern, $pos)); } // find the first optional token @@ -122,6 +129,22 @@ public function compile(Route $route) ); } + /** + * Adds a text token to the tokens array. + * + * @param array $tokens The route tokens + * @param string $text The static text + */ + private function addTextToken(array &$tokens, $text) + { + // when the last token is a text token, we can simply add the new text to it + if (false !== end($tokens) && 'text' === $tokens[key($tokens)][0]) { + $tokens[key($tokens)][1] .= $text; + } else { + $tokens[] = array('text', $text); + } + } + /** * Returns the next static character in the Route pattern that will serve as a separator. * @@ -135,8 +158,13 @@ private function findNextSeparator($pattern) // return empty string if pattern is empty or false (false which can be returned by substr) return ''; } - // first remove all placeholders from the pattern so we can find the next real static character - $pattern = preg_replace('#\{\w+\}#', '', $pattern); + + // first remove all (non-escaped) placeholders from the pattern so we can find the next real static character + $pattern = preg_replace('#(? 'default-z', '_format' => 'html'), array('y' => '(y|Y)')), @@ -140,6 +154,17 @@ public function provideCompileData() array('variable', '/', '[^/\.]+', 'w'), )), + array( + 'Route without separator between variables + escaped variables', + array('/\{w}{x}\{z}{y}\\\\{z}.'), + '/{w}', '#^/\{w\}(?[^/]+)\{z\}(?[^/]+)\\\\\\{z\}\.$#s', array('x', 'y'), array( + array('text', '\{z}.'), + array('variable', '', '[^/]+', 'y'), + array('text', '{z}'), + array('variable', '', '[^/]+', 'x'), + array('text', '/{w}'), + )), + array( 'Route with a format', array('/foo/{bar}.{_format}'), From 4338385b75bd3525315ef6acde421112d42a1075 Mon Sep 17 00:00:00 2001 From: Tobias Schultze Date: Wed, 3 Oct 2012 20:05:05 +0200 Subject: [PATCH 2/2] [Routing] added matcher and generator tests for escaped variables --- .../Routing/Tests/Generator/UrlGeneratorTest.php | 8 ++++++++ .../Component/Routing/Tests/Matcher/UrlMatcherTest.php | 9 +++++++++ 2 files changed, 17 insertions(+) diff --git a/src/Symfony/Component/Routing/Tests/Generator/UrlGeneratorTest.php b/src/Symfony/Component/Routing/Tests/Generator/UrlGeneratorTest.php index 9333e759e2c7c..f4cfccd0818fa 100644 --- a/src/Symfony/Component/Routing/Tests/Generator/UrlGeneratorTest.php +++ b/src/Symfony/Component/Routing/Tests/Generator/UrlGeneratorTest.php @@ -312,6 +312,14 @@ public function testDefaultRequirementOfVariableDisallowsNextSeparator() $this->getGenerator($routes)->generate('test', array('page' => 'do.t', '_format' => 'html')); } + public function testEscapedVariable() + { + $routes = $this->getRoutes('test', new Route('/{foo}\{static}{bar}')); + $generator = $this->getGenerator($routes); + + $this->assertSame('/app.php/foo%7Bstatic%7Dbar', $generator->generate('test', array('foo' => 'foo', 'bar' => 'bar'))); + } + protected function getGenerator(RouteCollection $routes, array $parameters = array(), $logger = null) { $context = new RequestContext('/app.php'); diff --git a/src/Symfony/Component/Routing/Tests/Matcher/UrlMatcherTest.php b/src/Symfony/Component/Routing/Tests/Matcher/UrlMatcherTest.php index 0927c750443a3..93b3cea79734e 100644 --- a/src/Symfony/Component/Routing/Tests/Matcher/UrlMatcherTest.php +++ b/src/Symfony/Component/Routing/Tests/Matcher/UrlMatcherTest.php @@ -309,6 +309,15 @@ public function testDefaultRequirementOfVariableDisallowsNextSeparator() $matcher->match('/do.t.html'); } + public function testEscapedVariable() + { + $coll = new RouteCollection(); + $coll->add('test', new Route('/{foo}\{static}{bar}')); + $matcher = new UrlMatcher($coll, new RequestContext()); + + $this->assertEquals(array('foo' => 'foo', 'bar' => 'bar', '_route' => 'test'), $matcher->match('/foo{static}bar')); + } + /** * @expectedException Symfony\Component\Routing\Exception\ResourceNotFoundException */