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,184 @@
<?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\Middleware;
use Psr\EventDispatcher\EventDispatcherInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use TYPO3\CMS\Core\Crypto\HashService;
use TYPO3\CMS\Core\Http\Uri;
use TYPO3\CMS\Core\Security\ContentSecurityPolicy\Configuration\CspConfigurationFactory;
use TYPO3\CMS\Core\Security\ContentSecurityPolicy\Configuration\DispositionConfiguration;
use TYPO3\CMS\Core\Security\ContentSecurityPolicy\Event\BeforePersistingReportEvent;
use TYPO3\CMS\Core\Security\ContentSecurityPolicy\PolicyProvider;
use TYPO3\CMS\Core\Security\ContentSecurityPolicy\Reporting\Report;
use TYPO3\CMS\Core\Security\ContentSecurityPolicy\Reporting\ReportDetails;
use TYPO3\CMS\Core\Security\ContentSecurityPolicy\Reporting\ReportRepository;
use TYPO3\CMS\Core\Security\ContentSecurityPolicy\Reporting\ReportStatus;
use TYPO3\CMS\Core\Security\ContentSecurityPolicy\Scope;
use TYPO3\CMS\Core\Utility\IpAnonymizationUtility;
/**
* @internal
*/
abstract class AbstractContentSecurityPolicyReporter implements MiddlewareInterface
{
protected const URI_KEYS = ['document-uri', 'report-uri', 'blocked-uri', 'referrer'];
public function __construct(
protected readonly EventDispatcherInterface $eventDispatcher,
protected readonly PolicyProvider $policyProvider,
protected readonly CspConfigurationFactory $cspConfigurationFactory,
protected readonly ReportRepository $reportRepository,
protected readonly HashService $hashService,
) {}
protected function persistCspReport(Scope $scope, ServerRequestInterface $request): void
{
$payload = (string)$request->getBody();
if (!$this->isJson($payload)) {
return;
}
$normalizedParams = $request->getAttribute('normalizedParams');
$meta = [
'addr' => IpAnonymizationUtility::anonymizeIp($normalizedParams->getRemoteAddress()),
'agent' => $normalizedParams->getHttpUserAgent(),
];
// skip potential externally injected violation reports
$requestTime = $this->getRequestQueryParam($request, 'requestTime');
$requestHash = $this->getRequestQueryParam($request, 'requestHash');
if ($requestTime === null
|| $requestHash === null
|| !$this->hashService->validateHmac($requestTime, self::class, $requestHash)
) {
return;
}
$originalDetails = json_decode($payload, true)['csp-report'] ?? [];
$originalDetails = $this->anonymizeDetails($originalDetails);
$details = new ReportDetails($originalDetails);
$summary = $this->generateReportSummary($scope, $details);
$report = new Report(
$scope,
ReportStatus::New,
(int)$requestTime,
$meta,
$details,
$summary
);
$event = $this->eventDispatcher->dispatch(
new BeforePersistingReportEvent($report, $request)
);
if ($event->report !== null) {
$this->reportRepository->add($event->report);
}
}
protected function generateReportSummary(Scope $scope, ReportDetails $details): string
{
return $this->hashService->hmac(
json_encode([
$scope,
$details['effective-directive'] ?? null,
$details['blocked-uri'] ?? null,
$details['script-sample'] ?? null,
]),
self::class,
);
}
protected function anonymizeDetails(array $details): array
{
foreach (self::URI_KEYS as $uriKey) {
if (!isset($details[$uriKey])) {
continue;
}
$details[$uriKey] = $this->anonymizeUri($details[$uriKey]);
}
return $details;
}
protected function anonymizeUri(string $value): string
{
try {
$uri = Uri::fromAnyScheme($value);
} catch (\Throwable) {
return '';
}
if ($uri->getQuery() === '') {
return $value;
}
parse_str($uri->getQuery(), $query);
// strip CSRF token (might be a different usage as well)
unset($query['token']);
return (string)$uri->withQuery(http_build_query($query, '', '&', PHP_QUERY_RFC3986));
}
/**
* Determines, whether current request URI starts with local reporting URI,
* (e.g. `https://ip12.anyhost.it:8443/en/@http-reporting?csp=report`).
*/
protected function targetsCspReportUri(Scope $scope, ServerRequestInterface $request): bool
{
$normalizedParams = $request->getAttribute('normalizedParams');
$reportingUriBase = $this->policyProvider->getDefaultReportingUriBase($scope, $request, false);
return str_starts_with($normalizedParams?->getRequestUri() ?? '', (string)$reportingUriBase);
}
/**
* Determines, whether the request is eligible to be handled by the local reporting URI
* (`targetsCspReportUri()` must have been called before).
*/
protected function isCspReport(
Scope $scope,
ServerRequestInterface $request,
?DispositionConfiguration $dispositionConfiguration = null,
): bool {
// @todo
// + verify current session
// + invoke rate limiter
// + check additional scope (snippet enrichment)
$reportingUrl = $this->policyProvider->getReportingUrlFor($scope, $request, $dispositionConfiguration);
$reportingUriBase = $this->policyProvider->getDefaultReportingUriBase($scope, $request);
$contentTypeHeader = $request->getHeaderLine('content-type');
return
// stop, if reporting was explicitly disabled in `contentSecurityPolicyReportingUrl`
$reportingUrl !== null
// stop, if different reporting URI was configured in `contentSecurityPolicyReportingUrl`
&& str_starts_with((string)$reportingUrl, (string)$reportingUriBase)
// stop, if request is not `POST` or `Content-Type` is not `application/csp-report`
&& $request->getMethod() === 'POST' && $contentTypeHeader === 'application/csp-report';
}
protected function getRequestQueryParam(ServerRequestInterface $request, string $name): ?string
{
$value = $request->getQueryParams()[$name] ?? null;
return is_string($value) ? $value : null;
}
protected function isJson(string $value): bool
{
try {
json_decode($value, false, 16, JSON_THROW_ON_ERROR);
return true;
} catch (\JsonException) {
return false;
}
}
}
@@ -0,0 +1,89 @@
<?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\Middleware;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
use TYPO3\CMS\Core\Context\Context;
use TYPO3\CMS\Core\Context\UserAspect;
use TYPO3\CMS\Core\Context\WorkspaceAspect;
/**
* Boilerplate to authenticate a backend user in the current workflow, can be used
* for TYPO3 Backend and Frontend requests.
*
* The actual authentication and the selection if no-cache headers to responses should
* be applied should still reside in the "process()" method which should be
* extended by derivative classes.
*
* In derivative classes, the Context API can be used to detect, if a backend user is logged in
* like this:
*
* ```
* $response = $handler->handle($request);
* if ($this->context->getAspect('backend.user')->isLoggedIn()) {
* return $this->applyHeadersToResponse($response);
* }
* ```
*
* @internal this class might get merged again with the subclasses
*/
abstract class BackendUserAuthenticator implements MiddlewareInterface
{
public function __construct(protected Context $context) {}
abstract public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface;
/**
* Adding headers to the response to avoid caching on the client side.
* These headers will override any previous headers of these names sent.
* Get the http headers to be sent if an authenticated user is available,
* in order to disallow browsers to store the response on the client side.
*
* @return ResponseInterface the modified response object.
*/
protected function applyHeadersToResponse(ResponseInterface $response): ResponseInterface
{
if ($response->getHeaderLine('Cache-Control')) {
return $response;
}
$headers = [
'Expires' => 0,
'Last-Modified' => gmdate('D, d M Y H:i:s') . ' GMT',
'Cache-Control' => 'no-cache, no-store',
// HTTP 1.0 compatibility, see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Pragma
'Pragma' => 'no-cache',
];
foreach ($headers as $headerName => $headerValue) {
$response = $response->withHeader($headerName, (string)$headerValue);
}
return $response;
}
/**
* Register the backend user as aspect
*/
protected function setBackendUserAspect(?BackendUserAuthentication $user, ?int $alternativeWorkspaceId = null): void
{
$this->context->setAspect('backend.user', new UserAspect($user));
$this->context->setAspect('workspace', new WorkspaceAspect($alternativeWorkspaceId ?? $user->workspace ?? 0));
}
}
@@ -0,0 +1,91 @@
<?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\Middleware;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
use TYPO3\CMS\Core\Attribute\AsEventListener;
use TYPO3\CMS\Core\Cache\CacheDataCollector;
use TYPO3\CMS\Core\Cache\CacheTag;
use TYPO3\CMS\Core\Cache\Event\AddCacheTagEvent;
/**
* Add CacheTags as 'cacheDataCollector' attribute.
*
* @internal
*/
class CacheDataCollectorAttribute implements MiddlewareInterface
{
/**
* The maximum length of the X-TYPO3-Cache-Tags header.
* Prevents exceeding the maximum header size of 8kB.
* Some web servers (e.g. nginx or apache2) have a default
* limit of 8kB for the size of a single header.
*/
private const int MAX_CACHE_TAGS_HEADER_LENGTH = 8000;
private ?CacheDataCollector $cacheDataCollector = null;
/**
* Adds an instance of TYPO3\CMS\Core\Cache\CacheDataCollector as
* attribute to $request object
*/
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
// Middlewares are shared services. With sub requests in mind, we need to take care
// existing data of the parent request is not overwritten by a sub request. We thus
// back up any existing data, create a new CacheDataCollector, dispatch the request,
// and reset before returning the response.
// @todo: We could argue sub requests should run their own container instance, but
// this has more impact and has not been sorted out, yet.
$backup = $this->cacheDataCollector;
$this->cacheDataCollector = new CacheDataCollector();
$request = $request->withAttribute('frontend.cache.collector', $this->cacheDataCollector);
$response = $handler->handle($request);
if ($this->isDebugModeEnabled()) {
$cacheTags = array_map(fn(CacheTag $cacheTag) => $cacheTag->name, $this->cacheDataCollector->getCacheTags());
sort($cacheTags);
foreach (explode("\n", wordwrap(implode(' ', $cacheTags), self::MAX_CACHE_TAGS_HEADER_LENGTH, "\n")) as $delta => $tags) {
$response = $response->withHeader('X-TYPO3-Cache-Tags' . ($delta > 0 ? '-' . $delta : ''), $tags);
}
$response = $response->withHeader('X-TYPO3-Cache-Lifetime', (string)$this->cacheDataCollector->resolveLifetime());
}
foreach ($this->cacheDataCollector->getCacheEntries() as $deferredCacheItem) {
$deferredCacheItem($request);
}
$this->cacheDataCollector = $backup;
return $response;
}
#[AsEventListener]
public function onCacheTagAdded(AddCacheTagEvent $event): void
{
// This event listener is a tribute to code places that want to add cache tags,
// but don't have the request available to set cache tags on the attribute directly.
// TYPO3 core will try to get rid of these places over time, but needs this event
// listener for now.
$this->cacheDataCollector?->addCacheTags($event->cacheTag);
}
private function isDebugModeEnabled(): bool
{
return !empty($GLOBALS['TYPO3_CONF_VARS']['FE']['debug']);
}
}
@@ -0,0 +1,46 @@
<?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\Middleware;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
use TYPO3\CMS\Core\Http\NormalizedParams;
/**
* Add NormalizedParams as 'normalizedParams' attribute.
* Used in FE, BE and install tool context.
*
* @internal
*/
readonly class NormalizedParamsAttribute implements MiddlewareInterface
{
/**
* Adds an instance of TYPO3\CMS\Core\Http\NormalizedParams as
* attribute to $request object
*/
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
$normalizedParams = $request->getAttribute('normalizedParams', null);
if ($normalizedParams === null) {
$request = $request->withAttribute('normalizedParams', NormalizedParams::createFromRequest($request));
}
return $handler->handle($request);
}
}
@@ -0,0 +1,165 @@
<?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\Middleware;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Psr\Log\LoggerInterface;
use Symfony\Component\HttpFoundation\Cookie;
use TYPO3\CMS\Core\Context\Context;
use TYPO3\CMS\Core\Context\SecurityAspect;
use TYPO3\CMS\Core\Http\NormalizedParams;
use TYPO3\CMS\Core\Security\Nonce;
use TYPO3\CMS\Core\Security\NonceException;
use TYPO3\CMS\Core\Security\NoncePool;
use TYPO3\CMS\Core\Security\RequestToken;
use TYPO3\CMS\Core\Security\RequestTokenException;
use TYPO3\CMS\Core\Utility\GeneralUtility;
/**
* @internal
*/
class RequestTokenMiddleware implements MiddlewareInterface
{
protected const COOKIE_PREFIX = 'typo3nonce_';
protected const SECURE_PREFIX = '__Secure-';
protected const ALLOWED_METHODS = ['POST', 'PUT', 'PATCH'];
protected SecurityAspect $securityAspect;
protected NoncePool $noncePool;
public function __construct(
Context $context,
protected readonly LoggerInterface $logger,
) {
$this->securityAspect = SecurityAspect::provideIn($context);
$this->noncePool = $this->securityAspect->getNoncePool();
}
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
// @todo some™ route handling mechanism might verify request-tokens (-> e.g. backend-routes, unsure for frontend)
$this->noncePool->merge($this->resolveNoncePool($request))->purge();
try {
$this->securityAspect->setReceivedRequestToken($this->resolveReceivedRequestToken($request));
} catch (RequestTokenException $exception) {
// request token was given, but could not be verified
$this->securityAspect->setReceivedRequestToken(false);
$this->logger->debug('Could not resolve request token', ['exception' => $exception]);
}
$response = $handler->handle($request);
return $this->enrichResponseWithCookie($request, $response);
}
protected function resolveNoncePool(ServerRequestInterface $request): NoncePool
{
$secure = $this->isHttps($request);
// resolves cookie name dependent on whether TLS is used in request and uses `__Secure-` prefix,
// see https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#cookie_prefixes
$securePrefix = $secure ? self::SECURE_PREFIX : '';
$cookiePrefix = $securePrefix . self::COOKIE_PREFIX;
$cookiePrefixLength = strlen($cookiePrefix);
$cookies = array_filter(
$request->getCookieParams(),
static fn(mixed $name): bool => is_string($name) && str_starts_with($name, $cookiePrefix),
ARRAY_FILTER_USE_KEY
);
$items = [];
foreach ($cookies as $name => $value) {
$name = substr($name, $cookiePrefixLength);
try {
$items[$name] = Nonce::fromHashSignedJwt($value);
} catch (NonceException $exception) {
$this->logger->debug('Could not resolve received nonce', ['exception' => $exception]);
$items[$name] = null;
}
}
// @todo pool `$options` should be configurable via `$TYPO3_CONF_VARS`
return GeneralUtility::makeInstance(NoncePool::class, $items);
}
/**
* @throws RequestTokenException
*/
protected function resolveReceivedRequestToken(ServerRequestInterface $request): ?RequestToken
{
$headerValue = $request->getHeaderLine(RequestToken::HEADER_NAME);
$paramValue = '';
if (isset($request->getParsedBody()[RequestToken::PARAM_NAME]) && is_scalar($request->getParsedBody()[RequestToken::PARAM_NAME])) {
$paramValue = (string)($request->getParsedBody()[RequestToken::PARAM_NAME]);
}
if ($headerValue !== '') {
$tokenValue = $headerValue;
} elseif (in_array($request->getMethod(), self::ALLOWED_METHODS, true)) {
$tokenValue = $paramValue;
} else {
$tokenValue = '';
}
if ($tokenValue === '') {
return null;
}
return RequestToken::fromHashSignedJwt($tokenValue, $this->securityAspect->getSigningSecretResolver());
}
protected function enrichResponseWithCookie(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface
{
$secure = $this->isHttps($request);
$normalizedParams = $request->getAttribute('normalizedParams');
$path = $normalizedParams->getSitePath();
$securePrefix = $secure ? self::SECURE_PREFIX : '';
$cookiePrefix = $securePrefix . self::COOKIE_PREFIX;
$createCookie = static fn(string $name, string $value, int $expire): Cookie => new Cookie(
$name,
$value,
$expire,
$path,
null,
$secure,
true,
false,
Cookie::SAMESITE_STRICT
);
$cookies = [];
// emit new nonce cookies
foreach ($this->noncePool->getEmittableNonces() as $name => $nonce) {
$cookies[] = $createCookie($cookiePrefix . $name, $nonce->toHashSignedJwt(), 0);
}
// revoke nonce cookies (exceeded pool size, expired or explicitly revoked)
foreach ($this->noncePool->getRevocableNames() as $name) {
$cookies[] = $createCookie($cookiePrefix . $name, '', -1);
}
// finally apply to response
foreach ($cookies as $cookie) {
$response = $response->withAddedHeader('Set-Cookie', (string)$cookie);
}
return $response;
}
protected function isHttps(ServerRequestInterface $request): bool
{
$normalizedParams = $request->getAttribute('normalizedParams');
return $normalizedParams instanceof NormalizedParams && $normalizedParams->isHttps();
}
}
@@ -0,0 +1,41 @@
<?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\Middleware;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
use TYPO3\CMS\Core\Http\PropagateResponseException;
/**
* @internal
*/
readonly class ResponsePropagation implements MiddlewareInterface
{
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
try {
$response = $handler->handle($request);
} catch (PropagateResponseException $e) {
$response = $e->getResponse();
}
return $response;
}
}
+125
View File
@@ -0,0 +1,125 @@
<?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\Middleware;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
/**
* Checks if the provided host header value matches the trusted hosts pattern.
*
* @internal
*/
class VerifyHostHeader implements MiddlewareInterface
{
public const ENV_TRUSTED_HOSTS_PATTERN_ALLOW_ALL = '.*';
public const ENV_TRUSTED_HOSTS_PATTERN_SERVER_NAME = 'SERVER_NAME';
protected string $trustedHostsPattern;
public function __construct(string $trustedHostsPattern)
{
$this->trustedHostsPattern = $trustedHostsPattern;
}
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
$serverParams = $request->getServerParams();
$httpHost = $serverParams['HTTP_HOST'] ?? '';
if (!$this->isAllowedHostHeaderValue($httpHost, $serverParams)) {
throw new \UnexpectedValueException(
'The current host header value does not match the configured trusted hosts pattern!'
. ' Check the pattern defined in $GLOBALS[\'TYPO3_CONF_VARS\'][\'SYS\'][\'trustedHostsPattern\']'
. ' and adapt it, if you want to allow the current host header \'' . $httpHost . '\' for your installation.',
1396795884
);
}
return $handler->handle($request);
}
/**
* Checks if the provided host header value matches the trusted hosts pattern.
*
* @param string $hostHeaderValue HTTP_HOST header value as sent during the request (may include port)
*/
public function isAllowedHostHeaderValue(string $hostHeaderValue, array $serverParams): bool
{
// Deny the value if trusted host patterns is empty, which means configuration is invalid.
if ($this->trustedHostsPattern === '') {
return false;
}
if ($this->trustedHostsPattern === self::ENV_TRUSTED_HOSTS_PATTERN_ALLOW_ALL) {
return true;
}
return $this->hostHeaderValueMatchesTrustedHostsPattern($hostHeaderValue, $serverParams);
}
/**
* Checks if the provided host header value matches the trusted hosts pattern without any preprocessing.
*/
protected function hostHeaderValueMatchesTrustedHostsPattern(string $hostHeaderValue, array $serverParams): bool
{
if ($this->trustedHostsPattern === self::ENV_TRUSTED_HOSTS_PATTERN_SERVER_NAME) {
$host = strtolower($hostHeaderValue);
// Default port to be verified if HTTP_HOST does not contain explicit port information.
// Deriving from raw/local webserver HTTPS information (not taking possible proxy configurations into account)
// as we compare against the raw/local server information (SERVER_PORT).
$port = self::webserverUsesHttps($serverParams) ? '443' : '80';
$parsedHostValue = parse_url('http://' . $host);
if (isset($parsedHostValue['port'])) {
$host = $parsedHostValue['host'];
$port = (string)$parsedHostValue['port'];
}
// Allow values that equal the server name
// Note that this is only secure if name base virtual host are configured correctly in the webserver
$hostMatch = $host === strtolower($serverParams['SERVER_NAME']) && $port === $serverParams['SERVER_PORT'];
} else {
// In case name based virtual hosts are not possible, we allow setting a trusted host pattern
// See https://typo3.org/teams/security/security-bulletins/typo3-core/typo3-core-sa-2014-001/ for further details
$hostMatch = (bool)preg_match('/^' . $this->trustedHostsPattern . '$/i', $hostHeaderValue);
}
return $hostMatch;
}
/**
* Determine if the webserver uses HTTPS.
*
* HEADS UP: This does not check if the client performed a
* HTTPS request, as possible proxies are not taken into
* account. It provides raw information about the current
* webservers configuration only.
*/
protected function webserverUsesHttps(array $serverParams): bool
{
if (!empty($serverParams['SSL_SESSION_ID'])) {
return true;
}
// https://secure.php.net/manual/en/reserved.variables.server.php
// "Set to a non-empty value if the script was queried through the HTTPS protocol."
return !empty($serverParams['HTTPS']) && strtolower($serverParams['HTTPS']) !== 'off';
}
}