diff --git a/src/lib/__tests__/email-html.test.ts b/src/lib/__tests__/email-html.test.ts index 268dd00c..415ac4ec 100644 --- a/src/lib/__tests__/email-html.test.ts +++ b/src/lib/__tests__/email-html.test.ts @@ -187,6 +187,39 @@ describe('blockExternalResources', () => { expect(html).not.toMatch(/<\/style>\s*` in + // one attribute and a `` in a later one made it "scrub" the + // attributes between them, decoding the CSS escapes into real quotes. + it('scrubs only real stylesheets, not text inside attribute values', () => { + const input = String.raw`click

z

`; + const { html } = prepareEmailHtml(input, { blockRemoteImages: true, isDark: false }); + expect(html).not.toMatch(/href="javascript:/i); + expect(html).toContain(String.raw`click`); + }); + + it('cuts external URLs out of a sheet and leaves its escapes as written', () => { + const css = String.raw`a{background:url(https://x/)} b{background:\5c 75rl(https://y/)} c{background:\75rl(https://z/)} d{content:"\"\3c /style\3e"}`; + const { html, blocked } = blockExternalResources(``); + expect(blocked).toBe(true); + // `\5c 75rl(` is the text `\75rl(`, not a url(): written decoded it + // became one. The escaped quote stays escaped. + expect(html).toBe(String.raw``); + }); + + it('reads an escape past the last code point as U+FFFD instead of throwing', () => { + expect(decodeCssEscapes('\\FFFFFF')).toBe('\ufffd'); + expect(() => blockExternalResources('')).not.toThrow(); + }); + + it('finds images the parser reads as , and entity-encoded URLs', () => { + expect(hasRemoteContent('')).toBe(true); + expect(hasRemoteContent('')).toBe(true); + expect(hasRemoteContent('')).toBe(true); + expect(hasRemoteContent('')).toBe(true); + const { html } = blockExternalResources(''); + expect(html).toContain('data-blocked-src="https://tracker.example/p.gif"'); + }); + it('finds the image behind a stray quote, as the browser does', () => { const { html, blocked } = blockExternalResources(''); expect(blocked).toBe(true); diff --git a/src/lib/email-html.ts b/src/lib/email-html.ts index f7e9cf9b..032f19c7 100644 --- a/src/lib/email-html.ts +++ b/src/lib/email-html.ts @@ -200,10 +200,15 @@ function serializeTag(name: string, attrs: ParsedAttr[], selfClosing: boolean): * the browser keeps them) and returns the list to write, `null` to keep the * attributes unchanged, or `false` to drop the tag. End tags are written * without attributes; comments, doctypes and bogus comments are dropped. + * `rawText`, when given, rewrites the text of each kept raw-text element + * (a `