diff --git a/packages/astro/src/server/middleware.ts b/packages/astro/src/server/middleware.ts index feb497488b80..34f0921c9905 100644 --- a/packages/astro/src/server/middleware.ts +++ b/packages/astro/src/server/middleware.ts @@ -279,8 +279,15 @@ function addMetaTagToHead(htmlChunk: string, metaTagsStr: string): string { return htmlChunk; } - const content = `${metaTagsStr}`; - return htmlChunk.replace('', content); + // Skip quoted attribute values so we don't match inside e.g. data-code="......" + let replaced = false; + return htmlChunk.replace(/"[^"]*"|'[^']*'|()/g, (match, headTag) => { + if (headTag && !replaced) { + replaced = true; + return `${metaTagsStr}`; + } + return match; + }); } function getMetaTagsStr({ diff --git a/packages/astro/test/server/middleware.test.ts b/packages/astro/test/server/middleware.test.ts index 81b6bab10d61..e66bb0565dca 100644 --- a/packages/astro/test/server/middleware.test.ts +++ b/packages/astro/test/server/middleware.test.ts @@ -428,6 +428,55 @@ describe('sentryMiddleware', () => { expect(resultFromNext).toBe(originalResponse); }); + it('does not inject meta tags into inside attribute values', async () => { + const middleware = handleRequest(); + + const ctx = { + ...DYNAMIC_REQUEST_CONTEXT, + }; + + const bodyWithHeadInAttr = + ''; + const next = vi.fn(() => + Promise.resolve( + new Response(bodyWithHeadInAttr, { + headers: new Headers({ 'content-type': 'text/html' }), + }), + ), + ); + + // @ts-expect-error, a partial ctx object is fine here + const resultFromNext = await middleware(ctx, next); + const html = await resultFromNext?.text(); + + expect(html).toContain(''); + expect(html).not.toContain('data-code="<html><head> inside quoted attribute values in the same chunk', async () => { + const middleware = handleRequest(); + + const ctx = { + ...DYNAMIC_REQUEST_CONTEXT, + }; + + const htmlWithHeadInDataAttr = '
'; + const next = vi.fn(() => + Promise.resolve( + new Response(htmlWithHeadInDataAttr, { + headers: new Headers({ 'content-type': 'text/html' }), + }), + ), + ); + + // @ts-expect-error, a partial ctx object is fine here + const resultFromNext = await middleware(ctx, next); + const html = await resultFromNext?.text(); + + expect(html).toContain(''); + expect(html).toContain('data-content="should not be modified"'); + }); + it("no-ops if there's no tag in the response", async () => { const middleware = handleRequest();