Skip to content

Commit bf09203

Browse files
Merge branch '8.0' into 8.1
* 8.0: Allow defining security provider factories without config [FrameworkBundle] Bump Request/Session value resolver priority above EntityValueResolver [Messenger] Ensure SigningSerializer won't decode before verifying the signature [Security] Remove the legacy nested unserialize() call from token and exception classes [Yaml] Reject non-stringables when using "!!binary" [Messenger][Amqp] delayed quorum queues [Notifier] Use `hash_equals()` to compare webhook signatures for Vonage [Mailer] Use `hash_equals()` to compare webhook signatures for AhaSend [Inflector][String] Fixed singularize `traces` > `trace` [AssetMapper] Warn on missing bare CSS and JSON imports When pushing, run GHA only on "*.*" branches [Console] Fix signal handler scoping [Security] Preserve webserver base URL in HttpUtils::createRequest()
2 parents 57598d9 + 3c29d01 commit bf09203

2 files changed

Lines changed: 72 additions & 4 deletions

File tree

HttpUtils.php

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -69,9 +69,17 @@ public function createRequest(Request $request, string $path): Request
6969
Request::setTrustedProxies([], Request::getTrustedHeaderSet());
7070
}
7171

72+
// Trusted proxies are disabled above, so getBaseUrl() now returns only the
73+
// webserver-derived portion of the base URL (e.g. an Apache "Alias /myapp …"
74+
// sub-directory install). That portion must remain in the generated sub-request
75+
// URI so it can re-detect its own base URL from SCRIPT_NAME/REQUEST_URI; only
76+
// the trusted-proxy prefix is dropped from the URL generator's context here,
77+
// otherwise it would be doubled once the sub-request is processed.
7278
$context = $this->urlGenerator?->getContext();
73-
if ($baseUrl = $context?->getBaseUrl()) {
74-
$context->setBaseUrl('');
79+
$contextBaseUrl = $context?->getBaseUrl();
80+
$realBaseUrl = null !== $context ? $request->getBaseUrl() : null;
81+
if ($resetBaseUrl = $contextBaseUrl !== $realBaseUrl) {
82+
$context->setBaseUrl($realBaseUrl);
7583
}
7684

7785
try {
@@ -80,8 +88,8 @@ public function createRequest(Request $request, string $path): Request
8088
if ($trustedProxies) {
8189
Request::setTrustedProxies($trustedProxies, Request::getTrustedHeaderSet());
8290
}
83-
if ($baseUrl) {
84-
$context->setBaseUrl($baseUrl);
91+
if ($resetBaseUrl) {
92+
$context->setBaseUrl($contextBaseUrl);
8593
}
8694
}
8795

Tests/HttpUtilsTest.php

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,66 @@ public function testCreateRequestFromRouteHandlesTrustedHeaders()
258258
);
259259
}
260260

261+
public function testCreateRequestFromRoutePreservesScriptNameBaseUrl()
262+
{
263+
// Sub-directory install (Apache "Alias /myapp /var/www/myapp/public" + mod_rewrite).
264+
// The master request's base URL comes from SCRIPT_NAME, NOT from X-Forwarded-Prefix.
265+
// The sub-request created for a `form_login.use_forward` login MUST inherit that base
266+
// URL so the URL generator (re-initialized from the sub-request via
267+
// RouterListener::onKernelRequest) emits form action URLs prefixed with `/myapp`.
268+
$server = [
269+
'REQUEST_URI' => '/myapp/',
270+
'SCRIPT_NAME' => '/myapp/index.php',
271+
'PHP_SELF' => '/myapp/index.php',
272+
'SCRIPT_FILENAME' => '/var/www/myapp/public/index.php',
273+
];
274+
$request = new Request([], [], [], [], [], $server);
275+
$this->assertSame('/myapp', $request->getBaseUrl());
276+
277+
$urlGenerator = new UrlGenerator(
278+
$routeCollection = new RouteCollection(),
279+
(new RequestContext())->fromRequest($request),
280+
);
281+
$routeCollection->add('app_login', new Route('/login'));
282+
283+
$subRequest = (new HttpUtils($urlGenerator))->createRequest($request, 'app_login');
284+
285+
$this->assertSame('/myapp', $subRequest->getBaseUrl());
286+
$this->assertSame('http://localhost/myapp/login', $subRequest->getUri());
287+
}
288+
289+
public function testCreateRequestFromRouteBehindProxyPreservesScriptNameBaseUrl()
290+
{
291+
// Sub-directory install (Apache "Alias /myapp …") behind a trusted proxy adding
292+
// an extra prefix: getBaseUrl() === "/proxy-prefix" + "/myapp". Only the
293+
// "/proxy-prefix" part may be dropped from the generated sub-request URI; the
294+
// "/myapp" part stays so the sub-request re-detects it from SCRIPT_NAME, and the
295+
// proxy prefix is re-added (not doubled) once the sub-request is processed.
296+
Request::setTrustedProxies(['127.0.0.1'], Request::HEADER_X_FORWARDED_PREFIX);
297+
298+
$server = [
299+
'REQUEST_URI' => '/myapp/',
300+
'SCRIPT_NAME' => '/myapp/index.php',
301+
'PHP_SELF' => '/myapp/index.php',
302+
'SCRIPT_FILENAME' => '/var/www/myapp/public/index.php',
303+
'REMOTE_ADDR' => '127.0.0.1',
304+
'HTTP_X_FORWARDED_PREFIX' => '/proxy-prefix',
305+
];
306+
$request = new Request([], [], [], [], [], $server);
307+
$this->assertSame('/proxy-prefix/myapp', $request->getBaseUrl());
308+
309+
$urlGenerator = new UrlGenerator(
310+
$routeCollection = new RouteCollection(),
311+
(new RequestContext())->fromRequest($request),
312+
);
313+
$routeCollection->add('app_login', new Route('/login'));
314+
315+
$subRequest = (new HttpUtils($urlGenerator))->createRequest($request, 'app_login');
316+
317+
$this->assertSame('/proxy-prefix/myapp', $subRequest->getBaseUrl());
318+
$this->assertSame('http://localhost/proxy-prefix/myapp/login', $subRequest->getUri());
319+
}
320+
261321
public function testCheckRequestPath()
262322
{
263323
$utils = new HttpUtils($this->getUrlGenerator());

0 commit comments

Comments
 (0)