diff --git a/cli/CHANGELOG.md b/cli/CHANGELOG.md index f99707ce519..05d8d2473d6 100644 --- a/cli/CHANGELOG.md +++ b/cli/CHANGELOG.md @@ -7,6 +7,7 @@ **Bugfixes:** +- Fixed an issue where the Cypress script was injected into a commented-out `
`, ``, or `` tag inside an HTML comment, corrupting the served page. Commented-out tags are no longer considered injection points. Fixes [#33000](https://github.com/cypress-io/cypress/issues/33000). - Fixed an issue where, on Windows, enhancing a test failure stack could throw a secondary `TypeError: Cannot read properties of undefined (reading 'replaceAll')` and mask the original error. Fixed in [#34252](https://github.com/cypress-io/cypress/pull/34252). - Fixed an issue where [`experimentalMemoryManagement`](https://on.cypress.io/experiments) could fail to prevent the browser from running out of memory and crashing when Cypress was running inside a memory-limited container. Memory is now managed correctly in these environments. Fixes [#34104](https://github.com/cypress-io/cypress/issues/34104). Addressed in [#34123](https://github.com/cypress-io/cypress/pull/34123). diff --git a/packages/proxy/lib/http/util/rewriter.ts b/packages/proxy/lib/http/util/rewriter.ts index 6512b79eb98..d04b580adc5 100644 --- a/packages/proxy/lib/http/util/rewriter.ts +++ b/packages/proxy/lib/http/util/rewriter.ts @@ -68,6 +68,75 @@ function getHtmlToInject (opts: InjectionOpts & SecurityOpts) { } } +// replaces HTML comments with same-length whitespace so the injection point +// regexes can't match tags inside comments, while every index stays aligned +// with the original html. a small scanner tracks tag and quoted-attribute +// state so ``, `--!>` or abruptly (``, ``), and +// an unterminated comment blanks the rest of the document +const maskHtmlComments = (html: string) => { + const masked: string[] = [] + let i = 0 + let inTag = false + let quote = '' + + while (i < html.length) { + const ch = html[i] + + if (inTag) { + if (quote) { + if (ch === quote) { + quote = '' + } + } else if (ch === '"' || ch === '\'') { + quote = ch + } else if (ch === '>') { + inTag = false + } + + masked.push(ch) + i++ + + continue + } + + if (html.startsWith('', i + 4) + const bangClose = html.indexOf('--!>', i + 4) + + if (normalClose !== -1 && (bangClose === -1 || normalClose < bangClose)) { + end = normalClose + 3 + } else if (bangClose !== -1) { + end = bangClose + 4 + } else { + end = html.length + } + } + + masked.push(' '.repeat(end - i)) + i = end + + continue + } + + if (ch === '<' && /[a-zA-Z!/?]/.test(html[i + 1] || '')) { + inTag = true + } + + masked.push(ch) + i++ + } + + return masked.join('') +} + const insertBefore = (originalString, match, stringToInsert) => { const index = match.index || 0 @@ -93,7 +162,11 @@ export async function html (html: string, opts: SecurityOpts & InjectionOpts) { return html } - const bootstrapMatch = html.match(bootstrapScriptRe) + // search a comment-masked copy so a commented-out tag can't become the + // injection point, then splice into the original html by index + const searchableHtml = maskHtmlComments(html) + + const bootstrapMatch = searchableHtml.match(bootstrapScriptRe) if (bootstrapMatch) { const contentToInject = htmlToInject.replace(/^ -->