import sanitizeHtml from 'sanitize-html' const ALLOWED_HTML_BLOCK_TAGS = [ 'a', 'abbr', 'b', 'blockquote', 'br', 'caption', 'code', 'col', 'colgroup', 'dd', 'details', 'div', 'dl', 'dt', 'em', 'figcaption', 'figure', 'h2', 'h3', 'h4', 'h5', 'h6', 'hr', 'i', 'img', 'li', 'mark', 'ol', 'p', 'pre', 's', 'small', 'span', 'strong', 'sub', 'summary', 'sup', 'table', 'tbody', 'td', 'tfoot', 'th', 'thead', 'time', 'tr', 'u', 'ul' ] const HTML_BLOCK_SANITIZE_OPTIONS = { allowedTags: ALLOWED_HTML_BLOCK_TAGS, allowedAttributes: { '*': ['class', 'title', 'aria-label', 'aria-hidden'], a: ['href', 'target', 'rel'], col: ['span', 'width'], img: ['src', 'alt', 'width', 'height', 'loading'], li: ['value'], ol: ['start', 'type'], td: ['colspan', 'rowspan', 'headers'], th: ['colspan', 'rowspan', 'headers', 'scope'], time: ['datetime'] }, allowedSchemes: ['http', 'https', 'mailto', 'tel'], allowedSchemesAppliedToAttributes: ['href', 'src'], allowProtocolRelative: false, nonTextTags: ['script', 'style', 'textarea', 'option', 'template', 'noscript'], transformTags: { a: (tagName, attribs) => { if (attribs.target !== '_blank') { return { tagName, attribs } } const relValues = new Set(String(attribs.rel || '').split(/\s+/).filter(Boolean)) relValues.add('noopener') relValues.add('noreferrer') return { tagName, attribs: { ...attribs, rel: [...relValues].join(' ') } } } } } /** * 본문 HTML 블록에서 실행성 코드와 위험 속성을 제거한다. * @param {string} value - 원본 HTML * @returns {string} 정리된 HTML */ export const sanitizeHtmlBlock = (value) => { const html = String(value || '').trim() return html ? sanitizeHtml(html, HTML_BLOCK_SANITIZE_OPTIONS).trim() : '' }