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,59 @@
<?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\Security\ContentSecurityPolicy\Configuration;
/**
* @internal
*/
final class Behavior
{
/**
* @param bool|null $useNonce Whether to use nonce values
* @param bool|null $useHash Whether to use hash values
*/
public function __construct(
/**
* Controls nonce usage. null = system decides per context, true = always use nonce, false = never use nonce.
*/
public ?bool $useNonce = null,
/**
* Whether to collect CSP hash values for assets. Always true by default because hashes enable
* response caching (unlike nonces which are per-request). Even when nonces are used, hashes are
* still collected so that cached responses include the correct CSP directives.
*/
public ?bool $useHash = null,
) {}
/**
* Creates a Behavior instance from a `csp.yaml` `behavior:` section.
*
* Example:
* ```yaml
* behavior:
* useNonce: false
* useHash: true
* ```
*/
public static function fromArray(array $data): self
{
$useNonce = isset($data['useNonce']) ? (bool)$data['useNonce'] : null;
$useHash = isset($data['useHash']) ? (bool)$data['useHash'] : null;
return new self($useNonce, $useHash);
}
}
@@ -0,0 +1,126 @@
<?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\Security\ContentSecurityPolicy\Configuration;
use TYPO3\CMS\Core\Configuration\Features;
use TYPO3\CMS\Core\Security\ContentSecurityPolicy\Disposition;
use TYPO3\CMS\Core\Type\Map;
/**
* Transforms a `csp.yaml` site configuration into a configuration model.
*
* @internal
*/
readonly class CspConfigurationFactory
{
public function __construct(private Features $features) {}
/**
* @return list<Disposition>
*/
public function resolveFallbackDispositions(): array
{
$dispositions = [];
if ($this->features->isFeatureEnabled('security.frontend.enforceContentSecurityPolicy')) {
$dispositions[] = Disposition::enforce;
}
if ($this->features->isFeatureEnabled('security.frontend.reportContentSecurityPolicy')) {
$dispositions[] = Disposition::report;
}
return $dispositions;
}
/**
* Builds a Behavior instance from the top-level `behavior:` section of the `csp.yaml` configuration.
*/
public function buildBehavior(array $siteConfiguration): Behavior
{
$behaviorData = $siteConfiguration['behavior'] ?? [];
return is_array($behaviorData) ? Behavior::fromArray($behaviorData) : new Behavior();
}
/**
* @return Map<Disposition, DispositionConfiguration>
*/
public function buildDispositionMap(array $siteConfiguration): Map
{
$activeAssignment = (bool)($siteConfiguration['active'] ?? true);
// @todo future TYPO3 v14 should explicitly require `active: true` to get rid of the feature fallbacks
if ($activeAssignment === false) {
return new Map();
}
$dispositions = new Map();
// assign site-specific dispositions
foreach (Disposition::cases() as $disposition) {
$assignment = $siteConfiguration[$disposition->value] ?? null;
if ($this->isActive($assignment)) {
$dispositions[$disposition] = $this->buildDispositionConfiguration(
$assignment,
$siteConfiguration
);
}
}
// in case there is no site-specific configuration, use the fallbacks as defined by top-level features
if (count($dispositions) === 0) {
foreach ($this->resolveFallbackDispositions() as $fallbackDisposition) {
// skip fallbacks in case the disposition was disabled explicitly (e.g. `enforce: false`)
if (($siteConfiguration[$fallbackDisposition->value] ?? null) !== false) {
$dispositions[$fallbackDisposition] = $this->buildDispositionConfiguration(
true,
$siteConfiguration
);
}
}
}
return $dispositions;
}
private function isActive(mixed $assignment): bool
{
return $assignment === true || is_array($assignment);
}
private function buildDispositionConfiguration(
true|array $assignment,
array $siteConfiguration = []
): DispositionConfiguration {
if ($assignment === true) {
// take from top-level configuration
// (`includeResolutions` and `packages` are ignored on purpose)
$inheritDefault = $siteConfiguration['inheritDefault'] ?? true;
$includeResolutions = true;
$reportingUrl = null;
$mutations = $siteConfiguration['mutations'] ?? [];
$packages = [];
} else {
$inheritDefault = $assignment['inheritDefault'] ?? true;
$includeResolutions = $assignment['includeResolutions'] ?? true;
$reportingUrl = $assignment['reportingUrl'] ?? null;
$mutations = $assignment['mutations'] ?? [];
$packages = $assignment['packages'] ?? [];
}
return new DispositionConfiguration(
(bool)$inheritDefault,
(bool)$includeResolutions,
$reportingUrl,
is_array($mutations) ? $mutations : [],
is_array($packages) ? $packages : [],
);
}
}
@@ -0,0 +1,76 @@
<?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\Security\ContentSecurityPolicy\Configuration;
/**
* Represents a `csp.yaml` site configuration section for the disposition modes `enforce` and `report.
*
* @internal
*/
readonly class DispositionConfiguration
{
public bool|string|null $reportingUrl;
public function __construct(
public bool $inheritDefault,
public bool $includeResolutions,
mixed $reportingUrl,
public array $mutations = [],
/** @var array<string, bool> $packages */
public array $packages = [],
) {
$this->reportingUrl = self::normalizeReportingUrl($reportingUrl);
}
public static function normalizeReportingUrl(mixed $reportingUrl): bool|string|null
{
if ($reportingUrl === null || is_bool($reportingUrl)) {
return $reportingUrl;
}
if (!is_scalar($reportingUrl)) {
return null;
}
if ($reportingUrl === 0 || $reportingUrl === '0') {
return false;
}
if ($reportingUrl === 1 || $reportingUrl === '1') {
return true;
}
return (string)$reportingUrl;
}
public function resolveEffectivePackages(string ...$packageNames): array
{
if ($this->packages === []) {
return $packageNames;
}
$effectivePackageNames = [];
if (($this->packages['*'] ?? null) === true) {
$effectivePackageNames = $packageNames;
}
$dropPackageNames = array_filter($packageNames, fn(string $package): bool => ($this->packages[$package] ?? null) === false);
$effectivePackageNames = array_diff($effectivePackageNames, $dropPackageNames);
$includePackageNames = array_filter($packageNames, fn(string $package): bool => ($this->packages[$package] ?? null) === true);
$effectivePackageNames = [...$effectivePackageNames, ...array_diff($includePackageNames, $effectivePackageNames)];
return $effectivePackageNames;
}
}