- 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
66 lines
2.8 KiB
TypeScript
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: '' })
|
|
}
|
|
})
|