diff --git a/.changeset/humble-humans-sink.md b/.changeset/humble-humans-sink.md new file mode 100644 index 000000000000..92302e191c51 --- /dev/null +++ b/.changeset/humble-humans-sink.md @@ -0,0 +1,5 @@ +--- +'@astrojs/node': patch +--- + +Fixes an issue where static headers weren't correctly applied when the website uses `base`. diff --git a/packages/integrations/node/src/serve-static.ts b/packages/integrations/node/src/serve-static.ts index 610c1f0e9fb9..917a24db5c6c 100644 --- a/packages/integrations/node/src/serve-static.ts +++ b/packages/integrations/node/src/serve-static.ts @@ -48,7 +48,13 @@ export function createStaticHandler( }); const routeData = app.match(request, true); if (routeData && routeData.prerender) { - const matchedRoute = headersMap.find((header) => header.pathname.includes(pathname)); + // Headers are stored keyed by base-less route paths (e.g. "/one"), so we + // must strip config.base from the incoming URL before matching, just as + // we do for filesystem access above. + const baselessPathname = prependForwardSlash(app.removeBase(urlPath)); + const matchedRoute = headersMap.find((header) => + header.pathname.includes(baselessPathname), + ); if (matchedRoute) { for (const header of matchedRoute.headers) { res.setHeader(header.key, header.value); diff --git a/packages/integrations/node/test/static-headers.test.js b/packages/integrations/node/test/static-headers.test.js index 86c124200879..3dc3d98f4c37 100644 --- a/packages/integrations/node/test/static-headers.test.js +++ b/packages/integrations/node/test/static-headers.test.js @@ -36,11 +36,13 @@ describe('Static headers', () => { before(async () => { fixture = await loadFixture({ root: './fixtures/static-headers/', + outDir: './dist/root-base', output: 'server', adapter: nodejs({ mode: 'standalone', staticHeaders: true }), }); await fixture.build(); const { startServer } = await fixture.loadAdapterEntryModule(); + process.env.PORT = '4322'; const res = startServer(); server = res.server; await waitServerListen(server.server); @@ -69,3 +71,49 @@ describe('Static headers', () => { ); }); }); + +describe('Static headers with non-root base', () => { + /** @type {import('./test-utils').Fixture} */ + let fixture; + let server; + + before(async () => { + fixture = await loadFixture({ + root: './fixtures/static-headers/', + outDir: './dist/non-root-base', + base: '/docs', + output: 'server', + adapter: nodejs({ mode: 'standalone', staticHeaders: true }), + }); + await fixture.build(); + const { startServer } = await fixture.loadAdapterEntryModule(); + process.env.PORT = '4323'; + const res = startServer(); + server = res.server; + await waitServerListen(server.server); + }); + + after(async () => { + await server.stop(); + }); + + it('CSP headers are added to the index route under the base path', async () => { + const res = await fetch(`http://${server.host}:${server.port}/docs/`); + const csp = res.headers.get('Content-Security-Policy'); + assert.ok(csp, 'Content-Security-Policy header must be present for the index route'); + assert.ok( + csp.includes('script-src'), + 'should contain script-src directive due to server island', + ); + }); + + it('CSP headers are added to a dynamic route under the base path', async () => { + const res = await fetch(`http://${server.host}:${server.port}/docs/one`); + const csp = res.headers.get('Content-Security-Policy'); + assert.ok(csp, 'Content-Security-Policy header must be present for dynamic routes'); + assert.ok( + csp.includes('script-src'), + 'should contain script-src directive due to server island', + ); + }); +});