mirror of
https://github.com/simstudioai/sim.git
synced 2026-09-24 15:45:35 +08:00
improvement(gmail): replace custom html-to-text regex with library (#4613)
* improvement(gmail): replace custom html-to-text regex with html-to-text library Resolves 4 CodeQL alerts on htmlToPlainText (incomplete tag/entity handling, unsafe regex backtracking). Delegates to the html-to-text npm package already used by the outlook polling trigger and the mail/send route. * improvement(gmail): match outlook selectors config, add nbsp/anchor tests Aligns html-to-text options with apps/sim/lib/webhooks/polling/outlook.ts: suppress anchor hrefs when identical to text, drop bare # anchors, skip img/script/style content. Adds tests for nbsp preservation and anchor behavior.
This commit is contained in:
@@ -81,9 +81,9 @@ describe('plainTextToHtml', () => {
|
||||
})
|
||||
|
||||
describe('htmlToPlainText', () => {
|
||||
it('strips tags, decodes entities, and collapses whitespace', () => {
|
||||
it('strips tags and decodes entities', () => {
|
||||
const result = htmlToPlainText('<p>Hi & bye</p><p>Line<br>break</p>')
|
||||
expect(result).toBe('Hi & bye\nLine\nbreak')
|
||||
expect(result).toBe('Hi & bye\n\nLine\nbreak')
|
||||
})
|
||||
|
||||
it('drops <style> and <script> contents', () => {
|
||||
@@ -97,10 +97,21 @@ describe('htmlToPlainText', () => {
|
||||
})
|
||||
|
||||
it('decodes decimal and hexadecimal numeric entities', () => {
|
||||
expect(htmlToPlainText('<p>“hi”  and’s</p>')).toBe(
|
||||
'\u201chi\u201d \u00a0and\u2019s'
|
||||
expect(htmlToPlainText('<p>“hi” and’s</p>')).toBe(
|
||||
'\u201chi\u201d and\u2019s'
|
||||
)
|
||||
})
|
||||
|
||||
it('preserves   (non-breaking space) as U+00A0 for fidelity in plain-text output', () => {
|
||||
expect(htmlToPlainText('<p>a b</p>')).toBe('a\u00a0b')
|
||||
})
|
||||
|
||||
it('elides anchor URLs that exactly match link text, and drops bare # anchors', () => {
|
||||
expect(
|
||||
htmlToPlainText('<p>Visit <a href="https://example.com">https://example.com</a></p>')
|
||||
).toBe('Visit https://example.com')
|
||||
expect(htmlToPlainText('<p><a href="#section">Anchor</a></p>')).toBe('Anchor')
|
||||
})
|
||||
})
|
||||
|
||||
describe('buildSimpleEmailMessage', () => {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { convert } from 'html-to-text'
|
||||
import type {
|
||||
GmailAttachment,
|
||||
GmailMessage,
|
||||
@@ -344,26 +345,21 @@ export function plainTextToHtml(body: string): string {
|
||||
}
|
||||
|
||||
/**
|
||||
* Best-effort conversion of an HTML body to a plain-text fallback. Strips tags
|
||||
* and decodes the common entities. Used so we always include a plain-text part
|
||||
* alongside HTML for clients that don't render HTML.
|
||||
* Best-effort conversion of an HTML body to a plain-text fallback. Used so we
|
||||
* always include a plain-text part alongside HTML for clients that don't render
|
||||
* HTML. Delegates to the `html-to-text` library for robust tag stripping and
|
||||
* entity decoding (also used elsewhere in the repo for the same purpose).
|
||||
*/
|
||||
export function htmlToPlainText(html: string): string {
|
||||
return html
|
||||
.replace(/<style[\s\S]*?<\/style>/gi, '')
|
||||
.replace(/<script[\s\S]*?<\/script>/gi, '')
|
||||
.replace(/<br\s*\/?>/gi, '\n')
|
||||
.replace(/<\/(p|div|h[1-6]|li|tr)>/gi, '\n')
|
||||
.replace(/<[^>]+>/g, '')
|
||||
.replace(/ /g, ' ')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/&#x([0-9a-f]+);/gi, (_, hex) => String.fromCodePoint(Number.parseInt(hex, 16)))
|
||||
.replace(/&#(\d+);/g, (_, dec) => String.fromCodePoint(Number.parseInt(dec, 10)))
|
||||
.replace(/&/g, '&')
|
||||
.replace(/\n{3,}/g, '\n\n')
|
||||
.trim()
|
||||
return convert(html, {
|
||||
wordwrap: false,
|
||||
selectors: [
|
||||
{ selector: 'a', options: { hideLinkHrefIfSameAsText: true, noAnchorUrl: true } },
|
||||
{ selector: 'img', format: 'skip' },
|
||||
{ selector: 'script', format: 'skip' },
|
||||
{ selector: 'style', format: 'skip' },
|
||||
],
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user