source: Klonkt/src/services/HtmlSanitizerService.js@ 7bc636b

main
Last change on this file since 7bc636b was 7bc636b, checked in by Robin <robin@…>, 4 months ago

Initial commit — PrutFolio v1 source (pulled from Hetzner /srv/prutfolio)

  • Property mode set to 100644
File size: 2.9 KB
Line 
1/**
2 * HtmlSanitizerService — clean user-authored HTML from the WYSIWYG editor
3 * before storing it in the DB.
4 *
5 * Pipeline order on the render side (posts.js):
6 * 1. content already sanitized HTML (this service ran on save)
7 * 2. autoembed → adds iframes for Spotify/YouTube/etc (server-controlled, safe)
8 * 3. shortcode replacement → adds custom embed HTML (server-controlled, safe)
9 *
10 * Shortcodes like [[track:UUID]] / [[album:Name]] / [[playlist:slug]] live in
11 * text nodes — sanitize-html preserves text, so they pass through untouched.
12 */
13
14import sanitizeHtml from 'sanitize-html';
15
16const ALLOWED_TAGS = [
17 // Block
18 'p', 'div', 'br', 'hr',
19 'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
20 'blockquote', 'pre',
21 'ul', 'ol', 'li',
22 'figure', 'figcaption',
23 'table', 'thead', 'tbody', 'tr', 'td', 'th',
24 // Inline
25 'strong', 'em', 'b', 'i', 'u', 's', 'mark', 'small', 'sub', 'sup',
26 'code', 'a', 'span', 'img',
27];
28
29// Per-tag attribute allowlist. '*' applies to every tag.
30const ALLOWED_ATTRS = {
31 '*': ['class', 'id', 'dir', 'lang', 'data-sc'],
32 a: ['href', 'title', 'target', 'rel'],
33 img: ['src', 'alt', 'title', 'width', 'height', 'loading'],
34};
35
36const ALLOWED_SCHEMES = ['http', 'https', 'mailto', 'tel'];
37const ALLOWED_SCHEMES_BY_TAG = {
38 img: ['http', 'https', 'data'],
39 a: ['http', 'https', 'mailto', 'tel'],
40};
41
42class HtmlSanitizerService {
43 /**
44 * Sanitize user HTML. Returns a clean string ready for DB storage.
45 * Empty input → empty string. Anything that would have rendered as a
46 * <script>, inline event handler, or javascript: URL is stripped.
47 */
48 static sanitize(html) {
49 if (!html || typeof html !== 'string') return '';
50 return sanitizeHtml(html, {
51 allowedTags: ALLOWED_TAGS,
52 allowedAttributes: ALLOWED_ATTRS,
53 allowedSchemes: ALLOWED_SCHEMES,
54 allowedSchemesByTag: ALLOWED_SCHEMES_BY_TAG,
55 // Drop entire <script>/<style> contents (default behaviour just strips
56 // tags and keeps inner text — we want the contents gone too).
57 nonTextTags: ['script', 'style', 'textarea', 'noscript'],
58 // Force external links to be safe-by-default. Server-side rewrite is
59 // simpler than a CSP header for this case.
60 transformTags: {
61 a: (tagName, attribs) => {
62 const out = { tagName, attribs: { ...attribs } };
63 const href = (attribs.href || '').trim();
64 if (/^https?:\/\//i.test(href)) {
65 out.attribs.target = out.attribs.target || '_blank';
66 out.attribs.rel = 'noopener noreferrer';
67 }
68 return out;
69 },
70 },
71 });
72 }
73
74 /**
75 * Plain-text extract for excerpts / search snippets. Removes ALL HTML
76 * (not the same as sanitize — this strips everything down to text).
77 */
78 static toPlainText(html) {
79 if (!html) return '';
80 return sanitizeHtml(html, { allowedTags: [], allowedAttributes: {} })
81 .replace(/\s+/g, ' ')
82 .trim();
83 }
84}
85
86export default HtmlSanitizerService;
Note: See TracBrowser for help on using the repository browser.