77 lines
4.0 KiB
TypeScript
77 lines
4.0 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
|
|
|
import { purifyNationHtml } from '../src/security/nationHtml.js';
|
|
|
|
describe('nation HTML purification', () => {
|
|
it('removes executable markup and unsafe URL/CSS vectors', () => {
|
|
const dirty = [
|
|
'<script>globalThis.__nationXss = true</script>',
|
|
'<img src="javascript:alert(1)" onerror="globalThis.__nationXss = true">',
|
|
'<img src="jav	ascript:alert(1)" srcset="data:image/svg+xml,attack 2x">',
|
|
'<a href="javascript:alert(2)" onclick="alert(3)">unsafe link</a>',
|
|
'<p style="background-image:url(javascript:alert(4));color:#fff" onmouseover="alert(5)">notice</p>',
|
|
'<iframe src="https://attacker.example/embed/1" onload="alert(6)"></iframe>',
|
|
'<iframe src="//www.youtube.com.evil.example/embed/1" srcdoc="<script>alert(7)</script>"></iframe>',
|
|
'<svg><a xlink:href="javascript:alert(8)">svg</a></svg>',
|
|
].join('');
|
|
|
|
const clean = purifyNationHtml(dirty);
|
|
|
|
expect(clean).not.toMatch(/script|onerror|onclick|onmouseover|onload|javascript:|background-image|attacker/i);
|
|
expect(clean).not.toContain('<img');
|
|
expect(clean).toContain('<a>unsafe link</a>');
|
|
expect(clean).toContain('<p style="color:#fff">notice</p>');
|
|
expect(clean).toContain('<iframe></iframe>');
|
|
});
|
|
|
|
it('does not throw on malformed escaped filenames and preserves the raw basename as alt text', () => {
|
|
expect(purifyNationHtml('<img src="/image/%E0%A4%A">')).toBe('<img src="/image/%E0%A4%A" alt="%E0%A4%A" />');
|
|
expect(purifyNationHtml('<img src="/image/a%20b.png"><img src="/image/a.png?x/y"><img src="x" alt="">')).toBe(
|
|
'<img src="/image/a%20b.png" alt="a%20b.png" /><img src="/image/a.png?x/y" alt="y" /><img src="x" alt="" />'
|
|
);
|
|
});
|
|
|
|
it('matches Ref iframe whitespace and case handling', () => {
|
|
expect(
|
|
purifyNationHtml(
|
|
[
|
|
'<iframe src=" https://www.youtube.com/embed/x "></iframe>',
|
|
'<iframe src="https://WWW.YouTube.COM/embed/x"></iframe>',
|
|
'<iframe src="https://www.youtube.com/Embed/x"></iframe>',
|
|
].join('')
|
|
)
|
|
).toBe('<iframe src="https://www.youtube.com/embed/x"></iframe><iframe></iframe><iframe></iframe>');
|
|
});
|
|
|
|
it('preserves Ref-compatible formatting, data-flip, images, and safe video embeds', () => {
|
|
const source = [
|
|
'<div class="notice" data-flip="horizontal" style="text-align:center;color:#00ffff">',
|
|
'<strong>북벌</strong><br>',
|
|
'<a href="https://example.com/path" target="_blank">계획</a>',
|
|
'<img src="/image/icons/default.jpg" srcset="javascript:alert(1) 2x, /image/icons/default.jpg 1x" alt="장수" width="64" height="64">',
|
|
'<iframe src="//www.youtube-nocookie.com/embed/abc123" width="560" height="315" allowfullscreen></iframe>',
|
|
'<iframe src="https://player.vimeo.com/video/1234" title="video"></iframe>',
|
|
'</div>',
|
|
].join('');
|
|
|
|
const clean = purifyNationHtml(source);
|
|
|
|
expect(clean).toContain('class="notice"');
|
|
expect(clean).toContain('data-flip="horizontal"');
|
|
expect(clean).toContain('style="text-align:center;color:#00ffff"');
|
|
expect(clean).toContain('<strong>북벌</strong><br />');
|
|
expect(clean).toContain('href="https://example.com/path"');
|
|
expect(clean).not.toContain('target="_blank"');
|
|
expect(clean).toContain('src="/image/icons/default.jpg"');
|
|
expect(clean).toContain('alt="장수"');
|
|
expect(clean).toContain('srcset="/image/icons/default.jpg 1x"');
|
|
expect(clean).toContain('//www.youtube-nocookie.com/embed/abc123');
|
|
expect(clean).toContain('https://player.vimeo.com/video/1234');
|
|
});
|
|
|
|
it('is idempotent for already-purified stored values', () => {
|
|
const first = purifyNationHtml('<p style="color:red"><b>방침</b></p>');
|
|
expect(purifyNationHtml(first)).toBe(first);
|
|
});
|
|
});
|