TASKS-1823 WebManager module
This commit is contained in:
@@ -0,0 +1,103 @@
|
||||
import sanitizeHtml from 'sanitize-html'
|
||||
|
||||
const ALLOWED_REL_VALUES = new Set(['nofollow', 'noopener', 'noreferrer'])
|
||||
|
||||
const ALLOWED_URL_PROTOCOLS = new Set(['http:', 'https:', 'mailto:', 'tel:'])
|
||||
|
||||
const URL_KEYS = new Set(['href', 'url', 'externalUrl'])
|
||||
|
||||
const RICH_TEXT_OPTIONS: sanitizeHtml.IOptions = {
|
||||
allowedTags: [
|
||||
'a',
|
||||
'b',
|
||||
'blockquote',
|
||||
'br',
|
||||
'code',
|
||||
'em',
|
||||
'h1',
|
||||
'h2',
|
||||
'h3',
|
||||
'h4',
|
||||
'h5',
|
||||
'h6',
|
||||
'hr',
|
||||
'i',
|
||||
'li',
|
||||
'ol',
|
||||
'p',
|
||||
'pre',
|
||||
's',
|
||||
'span',
|
||||
'strong',
|
||||
'u',
|
||||
'ul',
|
||||
],
|
||||
allowedAttributes: {
|
||||
'*': ['class', 'title'],
|
||||
'a': ['href', 'target', 'rel'],
|
||||
},
|
||||
allowedSchemes: ['http', 'https', 'mailto', 'tel'],
|
||||
allowProtocolRelative: true,
|
||||
disallowedTagsMode: 'discard',
|
||||
nonTextTags: [
|
||||
'script',
|
||||
'style',
|
||||
'textarea',
|
||||
'option',
|
||||
'iframe',
|
||||
'object',
|
||||
'embed',
|
||||
'form',
|
||||
'svg',
|
||||
'math',
|
||||
],
|
||||
transformTags: {
|
||||
a: (tagName, attribs) => {
|
||||
if (attribs.target !== undefined && !['_blank', '_self'].includes(attribs.target)) {
|
||||
delete attribs.target
|
||||
}
|
||||
if (attribs.rel !== undefined) {
|
||||
const rel = attribs.rel
|
||||
.split(/\s+/)
|
||||
.filter((value) => ALLOWED_REL_VALUES.has(value))
|
||||
.join(' ')
|
||||
if (rel) attribs.rel = rel
|
||||
else delete attribs.rel
|
||||
}
|
||||
if (attribs.target === '_blank') attribs.rel = 'noopener noreferrer'
|
||||
return { tagName, attribs }
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export function sanitizeRichText(value: string) {
|
||||
return sanitizeHtml(value, RICH_TEXT_OPTIONS)
|
||||
}
|
||||
|
||||
export function sanitizeUrl(value: string) {
|
||||
try {
|
||||
const parsed = new URL(value, 'https://relative.invalid')
|
||||
return ALLOWED_URL_PROTOCOLS.has(parsed.protocol) ? value : ''
|
||||
} catch {
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
export function sanitizePayload(value: unknown, key = ''): unknown {
|
||||
if (typeof value === 'string') {
|
||||
return URL_KEYS.has(key) ? sanitizeUrl(value) : sanitizeRichText(value)
|
||||
}
|
||||
|
||||
if (Array.isArray(value)) return value.map((item) => sanitizePayload(item))
|
||||
|
||||
if (value && typeof value === 'object') {
|
||||
return Object.fromEntries(
|
||||
Object.entries(value).map(([childKey, childValue]) => [
|
||||
childKey,
|
||||
sanitizePayload(childValue, childKey),
|
||||
])
|
||||
)
|
||||
}
|
||||
|
||||
return value
|
||||
}
|
||||
Reference in New Issue
Block a user