TYPO3 v15 dev-main snapshot ()

This commit is contained in:
2026-08-10 22:31:09 +02:00
commit af8cc155b5
6818 changed files with 642608 additions and 0 deletions
@@ -0,0 +1,23 @@
<?php
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
namespace TYPO3\CMS\Core\Http\Security;
use TYPO3\CMS\Core\Exception;
/**
* Exception thrown when route requires referrer, which does not match current base URL.
*/
class InvalidReferrerException extends Exception {}
@@ -0,0 +1,23 @@
<?php
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
namespace TYPO3\CMS\Core\Http\Security;
use TYPO3\CMS\Core\Exception;
/**
* Exception thrown when route requires referrer, which is missing.
*/
class MissingReferrerException extends Exception {}
+129
View File
@@ -0,0 +1,129 @@
<?php
declare(strict_types=1);
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
namespace TYPO3\CMS\Core\Http\Security;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use TYPO3\CMS\Core\Http\HtmlResponse;
use TYPO3\CMS\Core\Security\ContentSecurityPolicy\ConsumableNonce;
use TYPO3\CMS\Core\Security\ContentSecurityPolicy\Directive;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Core\Utility\PathUtility;
/**
* @internal
*/
readonly class ReferrerEnforcer
{
private const int TYPE_REFERRER_EMPTY = 1;
private const int TYPE_REFERRER_SAME_SITE = 2;
private const int TYPE_REFERRER_SAME_ORIGIN = 4;
public function handle(ServerRequestInterface $request, array $options): ?ResponseInterface
{
$requestHost = rtrim($this->resolveRequestHost($request), '/') . '/';
$requestDir = $this->resolveRequestDir($request);
$referrerType = $this->resolveReferrerType($request, $requestHost, $requestDir);
// valid referrer, no more actions required
if ($referrerType & self::TYPE_REFERRER_SAME_ORIGIN) {
return null;
}
$flags = $options['flags'] ?? [];
$expiration = $options['expiration'] ?? 5;
$nonce = $request->getAttribute('nonce');
// referrer is missing and route requested to refresh
// (created HTML refresh to enforce having referrer)
if (($request->getQueryParams()['referrer-refresh'] ?? 0) <= time()
&& (
in_array('refresh-always', $flags, true)
|| ($referrerType & self::TYPE_REFERRER_EMPTY && in_array('refresh-empty', $flags, true))
|| ($referrerType & self::TYPE_REFERRER_SAME_SITE && in_array('refresh-same-site', $flags, true))
)
) {
$refreshUri = $request->getUri();
parse_str($refreshUri->getQuery(), $queryParams);
$queryParams['referrer-refresh'] = time() + $expiration;
$refreshUri = $refreshUri->withQuery(
http_build_query($queryParams, '', '&', PHP_QUERY_RFC3986)
);
$scriptUri = $this->resolveAbsoluteWebPath(
'EXT:core/Resources/Public/JavaScript/referrer-refresh.js',
$request
);
$attributes = ['src' => $scriptUri];
if ($nonce instanceof ConsumableNonce) {
$attributes['nonce'] = $nonce->consumeStatic(Directive::ScriptSrcElem);
}
// simulating navigate event by clicking anchor link
// since meta-refresh won't change `document.referrer` in e.g. Firefox
return new HtmlResponse(sprintf(
'<html>'
. '<head><link rel="icon" href="data:image/svg+xml,"></head>'
. '<body><a href="%s" id="referrer-refresh">&nbsp;</a>'
. '<script %s></script></body>'
. '</html>',
htmlspecialchars((string)$refreshUri),
GeneralUtility::implodeAttributes($attributes, true)
));
}
$subject = $options['subject'] ?? '';
if ($referrerType & self::TYPE_REFERRER_EMPTY) {
// still empty referrer or invalid referrer, deny route invocation
throw new MissingReferrerException(
sprintf('Missing referrer%s', $subject !== '' ? ' for ' . $subject : ''),
1588095935
);
}
// referrer is given, but does not match current base URL
throw new InvalidReferrerException(
sprintf('Invalid referrer%s', $subject !== '' ? ' for ' . $subject : ''),
1588095936
);
}
protected function resolveAbsoluteWebPath(string $target, ServerRequestInterface $request): string
{
return (string)PathUtility::getSystemResourceUri($target, $request);
}
protected function resolveReferrerType(ServerRequestInterface $request, string $requestHost, string $requestDir): int
{
$referrer = $request->getServerParams()['HTTP_REFERER'] ?? '';
if ($referrer === '') {
return self::TYPE_REFERRER_EMPTY;
}
if (str_starts_with($referrer, $requestDir)) {
// same-origin implies same-site
return self::TYPE_REFERRER_SAME_ORIGIN | self::TYPE_REFERRER_SAME_SITE;
}
if (str_starts_with($referrer, $requestHost)) {
return self::TYPE_REFERRER_SAME_SITE;
}
return 0;
}
protected function resolveRequestHost(ServerRequestInterface $request): string
{
return $request->getAttribute('normalizedParams')->getRequestHost();
}
protected function resolveRequestDir(ServerRequestInterface $request): string
{
return $request->getAttribute('normalizedParams')->getRequestDir();
}
}