Files
vue-journal-layer/tests/htmlGate.test.ts
Jakub Zych a4dca42d77 test(16-05): Journal layer util tests
- node --test over routes, client (fake fetcher, include_empty=1),
  pagination, htmlGate (Go FormatHTML parity cases), sitemap, seo and
  pl/en i18n parity
- package script test and a Testing line in the README
2026-10-09 20:03:46 +02:00

66 lines
2.8 KiB
TypeScript

// The HTML gate mirrors the Journal plugin's FormatHTML / CheckHTML gate.
// The unsafe cases are the six of TestFormatHTMLRejectsUnsafeHTML in
// sm-journal-plugin classes/format_html_test.go, as rendered HTML.
import { test } from 'node:test'
import assert from 'node:assert/strict'
import { DANGEROUS_URL, EVENT_HANDLER, UNSAFE_TAG, gateJournalHtml, isSafeJournalHtml } from '../app/utils/htmlGate.ts'
const GO_CASES: Array<[string, string]> = [
['script', '<script>alert(1)</script>'],
['iframe', '<iframe src="https://evil.test"></iframe>'],
['event', '<img src=x onerror="alert(1)">'],
['javascript', '<a href="javascript:alert(1)">x</a>'],
['vbscript', '<a href="vbscript:msgbox(1)">x</a>'],
['data', '<a href="data:text/html;base64,PHNjcmlwdD5hbGVydCgxKTwvc2NyaXB0Pg==">x</a>'],
]
test('the patterns are the Go regex sources, case-insensitive', () => {
assert.equal(UNSAFE_TAG.source, '<(?:script|iframe|object|embed)\\b')
assert.equal(EVENT_HANDLER.source, '\\son[a-z]+\\s*=')
assert.equal(DANGEROUS_URL.source, '(?:javascript|vbscript|data):')
for (const re of [UNSAFE_TAG, EVENT_HANDLER, DANGEROUS_URL]) assert.ok(re.flags.includes('i'))
})
for (const [name, html] of GO_CASES) {
test(`Go case ${name} is unsafe`, () => {
assert.equal(isSafeJournalHtml(html), false)
assert.deepEqual(gateJournalHtml(html), { safe: false, html: '' })
})
test(`Go case ${name} in upper case is unsafe`, () => {
const upper = html.toUpperCase()
assert.equal(isSafeJournalHtml(upper), false)
assert.deepEqual(gateJournalHtml(upper), { safe: false, html: '' })
})
}
test('object and embed tags and spaced handlers are unsafe', () => {
assert.equal(isSafeJournalHtml('<object data="x"></object>'), false)
assert.equal(isSafeJournalHtml('<embed src="x">'), false)
assert.equal(isSafeJournalHtml('<div\nonclick = "x">'), false)
})
test('headings, tables, footnotes and links are safe and pass unchanged', () => {
const safe = [
'<h1>Hello</h1>',
'<table><thead><tr><th>a</th><th>b</th></tr></thead><tbody><tr><td>1</td><td>2</td></tr></tbody></table>',
'<p>See footnote.<sup id="fnref:1"><a href="#fn:1" class="footnote-ref">1</a></sup></p>',
'<p><a href="https://grzybyfunkcjonalne.pl/blog">Artykuły</a> <img src="/storage/app/media/las.webp" alt="las"></p>',
]
for (const html of safe) {
assert.equal(isSafeJournalHtml(html), true, html)
assert.deepEqual(gateJournalHtml(html), { safe: true, html })
}
})
test('words starting with "on" are not handlers without an =', () => {
assert.equal(isSafeJournalHtml('<p class="content">one of the onions</p>'), true)
})
test('empty input is safe and empty', () => {
for (const html of [null, undefined, '']) {
assert.equal(isSafeJournalHtml(html), true)
assert.deepEqual(gateJournalHtml(html), { safe: true, html: '' })
}
})