From b886838e851aa244ecc34d6105776085de72c9f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20REYNAUD?= Date: Sat, 9 May 2026 13:00:11 +0200 Subject: [PATCH] fix: access control headers forwarding --- src/CdnPhpBundle.php | 2 ++ src/Proxy.php | 10 +++++++- tests/ProxyTest.php | 54 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 65 insertions(+), 1 deletion(-) diff --git a/src/CdnPhpBundle.php b/src/CdnPhpBundle.php index fac8d5a..21db7b8 100644 --- a/src/CdnPhpBundle.php +++ b/src/CdnPhpBundle.php @@ -35,6 +35,7 @@ public function configure(DefinitionConfigurator $definition): void ->scalarNode('url')->isRequired()->end() ->booleanNode('check_assets')->defaultTrue()->end() ->booleanNode('encrypted_parameters')->defaultFalse()->end() + ->scalarNode('cors_allow_origin')->defaultValue('*')->end() ->end() ->end() // proxy ->arrayNode('encrypter')->addDefaultsIfNotSet() @@ -66,6 +67,7 @@ public function loadExtension(array $config, ContainerConfigurator $container, C ->arg('$assetsPath', $config['proxy']['assets_path']) ->arg('$checkAssets', $config['proxy']['check_assets']) ->arg('$cdnPhpUrl', $config['proxy']['url']) + ->arg('$corsAllowOrigin', $config['proxy']['cors_allow_origin']) ; $container->services() diff --git a/src/Proxy.php b/src/Proxy.php index f4dc93c..0555778 100644 --- a/src/Proxy.php +++ b/src/Proxy.php @@ -32,6 +32,7 @@ public function __construct( private readonly HttpClientInterface $client, private readonly string $cdnPhpUrl, private readonly Signer $signer, + private readonly ?string $corsAllowOrigin = null, private readonly ?FallbackHandlerInterface $fallbackHandler = null, ) { parent::__construct($assetsPath); @@ -88,11 +89,18 @@ public function response(string $file, ?Options $options = null, array $headers } foreach ($responseHeaders as $header => $values) { - if (true === str_starts_with($header, 'x-') || true === str_starts_with($header, 'cf-')) { + if (true === str_starts_with($header, 'x-') + || true === str_starts_with($header, 'cf-') + || true === str_starts_with($header, 'access-control-') + ) { $newResponse->headers->set($header, $values); } } + if (null !== $this->corsAllowOrigin) { + $newResponse->headers->set('Access-Control-Allow-Origin', $this->corsAllowOrigin); + } + $newResponse->headers->set(AbstractSessionListener::NO_AUTO_CACHE_CONTROL_HEADER, 'true'); } catch (\Throwable $e) { if ($this->fallbackHandler instanceof FallbackHandlerInterface) { diff --git a/tests/ProxyTest.php b/tests/ProxyTest.php index fdda344..fe7fff1 100644 --- a/tests/ProxyTest.php +++ b/tests/ProxyTest.php @@ -46,6 +46,7 @@ private function makeProxy( bool $checkAssets = false, ?FallbackHandlerInterface $fallback = null, ?Signer $signer = null, + ?string $corsAllowOrigin = '*', ): Proxy { return new Proxy( self::ASSETS_PATH, @@ -54,6 +55,7 @@ private function makeProxy( $client, self::CDN_URL, $signer ?? new Signer(), + $corsAllowOrigin, $fallback, ); } @@ -137,6 +139,58 @@ public function testResponseForwardsAllXPrefixedHeaders(): void self::assertSame('my-value', $response->headers->get('x-my-custom')); } + public function testResponseForwardsAccessControlHeadersFromCdn(): void + { + $client = new MockHttpClient( + new MockResponse( + 'font-content', + [ + 'response_headers' => [ + 'access-control-allow-origin' => ['https://example.com'], + 'access-control-expose-headers' => ['Content-Length'], + ], + ] + ) + ); + + $response = $this->makeProxy($client, corsAllowOrigin: null)->response('font.woff2'); + + self::assertSame('https://example.com', $response->headers->get('access-control-allow-origin')); + self::assertSame('Content-Length', $response->headers->get('access-control-expose-headers')); + } + + public function testResponseSetsCorsAllowOriginWhenConfigured(): void + { + $client = new MockHttpClient(new MockResponse('font-content')); + + $response = $this->makeProxy($client, corsAllowOrigin: '*')->response('font.woff2'); + + self::assertSame('*', $response->headers->get('access-control-allow-origin')); + } + + public function testResponseDoesNotSetCorsHeaderWhenCorsAllowOriginIsNull(): void + { + $client = new MockHttpClient(new MockResponse('font-content')); + + $response = $this->makeProxy($client, corsAllowOrigin: null)->response('font.woff2'); + + self::assertFalse($response->headers->has('access-control-allow-origin')); + } + + public function testConfiguredCorsOriginOverridesCdnCorsHeader(): void + { + $client = new MockHttpClient( + new MockResponse( + 'font-content', + ['response_headers' => ['access-control-allow-origin' => ['https://cdn.example.com']]] + ) + ); + + $response = $this->makeProxy($client, corsAllowOrigin: '*')->response('font.woff2'); + + self::assertSame('*', $response->headers->get('access-control-allow-origin')); + } + public function testResponseForwardsAllCfPrefixedHeaders(): void { $client = new MockHttpClient(