Skip to content

Commit 47a50b2

Browse files
Merge branch '4.1' into 4.2
* 4.1: [Routing] fix trailing slash redirections involving a trailing var [EventDispatcher] Revers event tracing order [Security] Prefer clone over unserialize(serialize()) for user refreshment [Console] OutputFormatter: move strtolower to createStyleFromString Adjust tests to work in the armhf architecture. Fixes #29281. Vietnamese translations improvement [Form] Fixed FormErrorIterator class phpdoc Renamed test controller from Controller to TestController so it doesn't show up in the IDE autocomplete. Don't use he in docs when its not needed EventSubscriberInterface isn't a man fixed public directory of web server and assets install when configured in composer.json
2 parents 6475852 + 369dd6f commit 47a50b2

18 files changed

+1245
-1240
lines changed

Matcher/Dumper/PhpMatcherDumper.php

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,7 @@ private function compileStaticRoutes(array $staticRoutes, array &$conditions): s
209209
foreach ($staticRoutes as $url => $routes) {
210210
$code .= self::export($url)." => array(\n";
211211
foreach ($routes as $name => list($route, $hasTrailingSlash)) {
212-
$code .= $this->compileRoute($route, $name, !$route->compile()->getHostVariables() ? $route->getHost() : $route->compile()->getHostRegex() ?: null, $hasTrailingSlash, $conditions);
212+
$code .= $this->compileRoute($route, $name, !$route->compile()->getHostVariables() ? $route->getHost() : $route->compile()->getHostRegex() ?: null, $hasTrailingSlash, false, $conditions);
213213
}
214214
$code .= "),\n";
215215
}
@@ -321,8 +321,9 @@ private function compileDynamicRoutes(RouteCollection $collection, bool $matchHo
321321
if ($hasTrailingSlash = '/' !== $regex && '/' === $regex[-1]) {
322322
$regex = substr($regex, 0, -1);
323323
}
324+
$hasTrailingVar = (bool) preg_match('#\{\w+\}/?$#', $route->getPath());
324325

325-
$tree->addRoute($regex, array($name, $regex, $state->vars, $route, $hasTrailingSlash));
326+
$tree->addRoute($regex, array($name, $regex, $state->vars, $route, $hasTrailingSlash, $hasTrailingVar));
326327
}
327328

328329
$code .= $this->compileStaticPrefixCollection($tree, $state, 0, $conditions);
@@ -331,7 +332,7 @@ private function compileDynamicRoutes(RouteCollection $collection, bool $matchHo
331332
$code .= "\n .')'";
332333
$state->regex .= ')';
333334
}
334-
$rx = ")(?:/?)$}{$modifiers}";
335+
$rx = ")/?$}{$modifiers}";
335336
$code .= "\n .'{$rx}',";
336337
$state->regex .= $rx;
337338
$state->markTail = 0;
@@ -377,12 +378,12 @@ private function compileStaticPrefixCollection(StaticPrefixCollection $tree, \st
377378
continue;
378379
}
379380

380-
list($name, $regex, $vars, $route, $hasTrailingSlash) = $route;
381+
list($name, $regex, $vars, $route, $hasTrailingSlash, $hasTrailingVar) = $route;
381382
$compiledRoute = $route->compile();
382383
$vars = array_merge($state->hostVars, $vars);
383384

384385
if ($compiledRoute->getRegex() === $prevRegex) {
385-
$state->routes = substr_replace($state->routes, $this->compileRoute($route, $name, $vars, $hasTrailingSlash, $conditions), -3, 0);
386+
$state->routes = substr_replace($state->routes, $this->compileRoute($route, $name, $vars, $hasTrailingSlash, $hasTrailingVar, $conditions), -3, 0);
386387
continue;
387388
}
388389

@@ -393,7 +394,7 @@ private function compileStaticPrefixCollection(StaticPrefixCollection $tree, \st
393394
$state->regex .= $rx;
394395

395396
$prevRegex = $compiledRoute->getRegex();
396-
$state->routes .= sprintf("%s => array(\n%s),\n", $state->mark, $this->compileRoute($route, $name, $vars, $hasTrailingSlash, $conditions));
397+
$state->routes .= sprintf("%s => array(\n%s),\n", $state->mark, $this->compileRoute($route, $name, $vars, $hasTrailingSlash, $hasTrailingVar, $conditions));
397398
}
398399

399400
return $code;
@@ -402,7 +403,7 @@ private function compileStaticPrefixCollection(StaticPrefixCollection $tree, \st
402403
/**
403404
* Compiles a single Route to PHP code used to match it against the path info.
404405
*/
405-
private function compileRoute(Route $route, string $name, $vars, bool $hasTrailingSlash, array &$conditions): string
406+
private function compileRoute(Route $route, string $name, $vars, bool $hasTrailingSlash, bool $hasTrailingVar, array &$conditions): string
406407
{
407408
$defaults = $route->getDefaults();
408409

@@ -419,12 +420,13 @@ private function compileRoute(Route $route, string $name, $vars, bool $hasTraili
419420
}
420421

421422
return sprintf(
422-
" array(%s, %s, %s, %s, %s, %s),\n",
423+
" array(%s, %s, %s, %s, %s, %s, %s),\n",
423424
self::export(array('_route' => $name) + $defaults),
424425
self::export($vars),
425426
self::export(array_flip($route->getMethods()) ?: null),
426427
self::export(array_flip($route->getSchemes()) ?: null),
427428
self::export($hasTrailingSlash),
429+
self::export($hasTrailingVar),
428430
$condition
429431
);
430432
}

Matcher/Dumper/PhpMatcherTrait.php

Lines changed: 24 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,8 @@ public function match($pathinfo)
5454
} finally {
5555
$this->context->setScheme($scheme);
5656
}
57-
} elseif ('/' !== $pathinfo) {
58-
$pathinfo = '/' !== $pathinfo[-1] ? $pathinfo.'/' : substr($pathinfo, 0, -1);
57+
} elseif ('/' !== $trimmedPathinfo = rtrim($pathinfo, '/') ?: '/') {
58+
$pathinfo = $trimmedPathinfo === $pathinfo ? $pathinfo.'/' : $trimmedPathinfo;
5959
if ($ret = $this->doMatch($pathinfo, $allow, $allowSchemes)) {
6060
return $this->redirect($pathinfo, $ret['_route']) + $ret;
6161
}
@@ -67,13 +67,13 @@ public function match($pathinfo)
6767
throw new ResourceNotFoundException();
6868
}
6969

70-
private function doMatch(string $rawPathinfo, array &$allow = array(), array &$allowSchemes = array()): array
70+
private function doMatch(string $pathinfo, array &$allow = array(), array &$allowSchemes = array()): array
7171
{
7272
$allow = $allowSchemes = array();
73-
$pathinfo = rawurldecode($rawPathinfo) ?: '/';
73+
$pathinfo = rawurldecode($pathinfo) ?: '/';
74+
$trimmedPathinfo = rtrim($pathinfo, '/') ?: '/';
7475
$context = $this->context;
7576
$requestMethod = $canonicalMethod = $context->getMethod();
76-
$trimmedPathinfo = '/' !== $pathinfo && '/' === $pathinfo[-1] ? substr($pathinfo, 0, -1) : $pathinfo;
7777

7878
if ($this->matchHost) {
7979
$host = strtolower($context->getHost());
@@ -82,17 +82,17 @@ private function doMatch(string $rawPathinfo, array &$allow = array(), array &$a
8282
if ('HEAD' === $requestMethod) {
8383
$canonicalMethod = 'GET';
8484
}
85+
$supportsRedirections = 'GET' === $canonicalMethod && $this instanceof RedirectableUrlMatcherInterface;
8586

86-
foreach ($this->staticRoutes[$trimmedPathinfo] ?? array() as list($ret, $requiredHost, $requiredMethods, $requiredSchemes, $hasTrailingSlash, $condition)) {
87+
foreach ($this->staticRoutes[$trimmedPathinfo] ?? array() as list($ret, $requiredHost, $requiredMethods, $requiredSchemes, $hasTrailingSlash, , $condition)) {
8788
if ($condition && !($this->checkCondition)($condition, $context, 0 < $condition ? $request ?? $request = $this->request ?: $this->createRequest($pathinfo) : null)) {
8889
continue;
8990
}
9091

91-
if ('/' === $pathinfo || $hasTrailingSlash === ('/' === $pathinfo[-1])) {
92-
// no-op
93-
} elseif ($this instanceof RedirectableUrlMatcherInterface && (!$requiredMethods || isset($requiredMethods['GET'])) && 'GET' === $canonicalMethod) {
94-
return $allow = $allowSchemes = array();
95-
} else {
92+
if ('/' !== $pathinfo && $hasTrailingSlash === ($trimmedPathinfo === $pathinfo)) {
93+
if ($supportsRedirections && (!$requiredMethods || isset($requiredMethods['GET']))) {
94+
return $allow = $allowSchemes = array();
95+
}
9696
continue;
9797
}
9898

@@ -125,23 +125,24 @@ private function doMatch(string $rawPathinfo, array &$allow = array(), array &$a
125125

126126
foreach ($this->regexpList as $offset => $regex) {
127127
while (preg_match($regex, $matchedPathinfo, $matches)) {
128-
foreach ($this->dynamicRoutes[$m = (int) $matches['MARK']] as list($ret, $vars, $requiredMethods, $requiredSchemes, $hasTrailingSlash, $condition)) {
128+
foreach ($this->dynamicRoutes[$m = (int) $matches['MARK']] as list($ret, $vars, $requiredMethods, $requiredSchemes, $hasTrailingSlash, $hasTrailingVar, $condition)) {
129129
if ($condition && !($this->checkCondition)($condition, $context, 0 < $condition ? $request ?? $request = $this->request ?: $this->createRequest($pathinfo) : null)) {
130130
continue;
131131
}
132132

133-
if ('/' !== $pathinfo) {
134-
if ('/' === $pathinfo[-1]) {
135-
if (preg_match($regex, substr($pathinfo, 0, -1), $n) && $m === (int) $n['MARK']) {
136-
$matches = $n;
137-
} else {
138-
$hasTrailingSlash = true;
139-
}
133+
if ($trimmedPathinfo === $pathinfo || !$hasTrailingVar) {
134+
// no-op
135+
} elseif (preg_match($regex, $this->matchHost ? $host.'.'.$trimmedPathinfo : $trimmedPathinfo, $n) && $m === (int) $n['MARK']) {
136+
$matches = $n;
137+
} else {
138+
$hasTrailingSlash = true;
139+
}
140+
141+
if ('/' !== $pathinfo && $hasTrailingSlash === ($trimmedPathinfo === $pathinfo)) {
142+
if ($supportsRedirections && (!$requiredMethods || isset($requiredMethods['GET']))) {
143+
return $allow = $allowSchemes = array();
140144
}
141-
if ($hasTrailingSlash !== ('/' === $pathinfo[-1])) {
142-
if ($this instanceof RedirectableUrlMatcherInterface && (!$requiredMethods || isset($requiredMethods['GET'])) && 'GET' === $canonicalMethod) {
143-
return $allow = $allowSchemes = array();
144-
}
145+
if ($trimmedPathinfo === $pathinfo || !$hasTrailingVar) {
145146
continue;
146147
}
147148
}

Matcher/RedirectableUrlMatcher.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,11 +44,11 @@ public function match($pathinfo)
4444
} finally {
4545
$this->context->setScheme($scheme);
4646
}
47-
} elseif ('/' === $pathinfo) {
47+
} elseif ('/' === $trimmedPathinfo = rtrim($pathinfo, '/') ?: '/') {
4848
throw $e;
4949
} else {
5050
try {
51-
$pathinfo = '/' !== $pathinfo[-1] ? $pathinfo.'/' : substr($pathinfo, 0, -1);
51+
$pathinfo = $trimmedPathinfo === $pathinfo ? $pathinfo.'/' : $trimmedPathinfo;
5252
$ret = parent::match($pathinfo);
5353

5454
return $this->redirect($pathinfo, $ret['_route'] ?? null) + $ret;

Matcher/UrlMatcher.php

Lines changed: 20 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ public function match($pathinfo)
8484
{
8585
$this->allow = $this->allowSchemes = array();
8686

87-
if ($ret = $this->matchCollection(rawurldecode($pathinfo), $this->routes)) {
87+
if ($ret = $this->matchCollection(rawurldecode($pathinfo) ?: '/', $this->routes)) {
8888
return $ret;
8989
}
9090

@@ -134,49 +134,41 @@ protected function matchCollection($pathinfo, RouteCollection $routes)
134134
if ('HEAD' === $method = $this->context->getMethod()) {
135135
$method = 'GET';
136136
}
137-
$supportsTrailingSlash = '/' !== $pathinfo && '' !== $pathinfo && $this instanceof RedirectableUrlMatcherInterface;
137+
$supportsTrailingSlash = 'GET' === $method && $this instanceof RedirectableUrlMatcherInterface;
138+
$trimmedPathinfo = rtrim($pathinfo, '/') ?: '/';
138139

139140
foreach ($routes as $name => $route) {
140141
$compiledRoute = $route->compile();
141-
$staticPrefix = $compiledRoute->getStaticPrefix();
142+
$staticPrefix = rtrim($compiledRoute->getStaticPrefix(), '/');
142143
$requiredMethods = $route->getMethods();
143144

144145
// check the static prefix of the URL first. Only use the more expensive preg_match when it matches
145-
if ('' === $staticPrefix || 0 === strpos($pathinfo, $staticPrefix)) {
146-
// no-op
147-
} elseif (!$supportsTrailingSlash || ($requiredMethods && !\in_array('GET', $requiredMethods)) || 'GET' !== $method) {
148-
continue;
149-
} elseif ('/' === $staticPrefix[-1] && substr($staticPrefix, 0, -1) === $pathinfo) {
150-
return $this->allow = $this->allowSchemes = array();
151-
} elseif ('/' === $pathinfo[-1] && substr($pathinfo, 0, -1) === $staticPrefix) {
152-
return $this->allow = $this->allowSchemes = array();
153-
} else {
146+
if ('' !== $staticPrefix && 0 !== strpos($trimmedPathinfo, $staticPrefix)) {
154147
continue;
155148
}
156149
$regex = $compiledRoute->getRegex();
157150

158-
if ($supportsTrailingSlash) {
159-
$pos = strrpos($regex, '$');
160-
$hasTrailingSlash = '/' === $regex[$pos - 1];
161-
$regex = substr_replace($regex, '/?$', $pos - $hasTrailingSlash, 1 + $hasTrailingSlash);
162-
}
151+
$pos = strrpos($regex, '$');
152+
$hasTrailingSlash = '/' === $regex[$pos - 1];
153+
$regex = substr_replace($regex, '/?$', $pos - $hasTrailingSlash, 1 + $hasTrailingSlash);
163154

164155
if (!preg_match($regex, $pathinfo, $matches)) {
165156
continue;
166157
}
167158

168-
if ($supportsTrailingSlash) {
169-
if ('/' === $pathinfo[-1]) {
170-
if (preg_match($regex, substr($pathinfo, 0, -1), $m)) {
171-
$matches = $m;
172-
} else {
173-
$hasTrailingSlash = true;
174-
}
159+
if ($trimmedPathinfo === $pathinfo || !$hasTrailingVar = preg_match('#\{\w+\}/?$#', $route->getPath())) {
160+
// no-op
161+
} elseif (preg_match($regex, $trimmedPathinfo, $m)) {
162+
$matches = $m;
163+
} else {
164+
$hasTrailingSlash = true;
165+
}
166+
167+
if ('/' !== $pathinfo && $hasTrailingSlash === ($trimmedPathinfo === $pathinfo)) {
168+
if ($supportsTrailingSlash && (!$requiredMethods || \in_array('GET', $requiredMethods))) {
169+
return $this->allow = $this->allowSchemes = array();
175170
}
176-
if ($hasTrailingSlash !== ('/' === $pathinfo[-1])) {
177-
if ((!$requiredMethods || \in_array('GET', $requiredMethods)) && 'GET' === $method) {
178-
return $this->allow = $this->allowSchemes = array();
179-
}
171+
if ($trimmedPathinfo === $pathinfo || !$hasTrailingVar) {
180172
continue;
181173
}
182174
}

0 commit comments

Comments
 (0)