1 | /**
|
---|
2 | * --------------------------------------------------------------------------
|
---|
3 | * Bootstrap util/sanitizer.js
|
---|
4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
---|
5 | * --------------------------------------------------------------------------
|
---|
6 | */
|
---|
7 |
|
---|
8 | // js-docs-start allow-list
|
---|
9 | const ARIA_ATTRIBUTE_PATTERN = /^aria-[\w-]*$/i
|
---|
10 |
|
---|
11 | export const DefaultAllowlist = {
|
---|
12 | // Global attributes allowed on any supplied element below.
|
---|
13 | '*': ['class', 'dir', 'id', 'lang', 'role', ARIA_ATTRIBUTE_PATTERN],
|
---|
14 | a: ['target', 'href', 'title', 'rel'],
|
---|
15 | area: [],
|
---|
16 | b: [],
|
---|
17 | br: [],
|
---|
18 | col: [],
|
---|
19 | code: [],
|
---|
20 | dd: [],
|
---|
21 | div: [],
|
---|
22 | dl: [],
|
---|
23 | dt: [],
|
---|
24 | em: [],
|
---|
25 | hr: [],
|
---|
26 | h1: [],
|
---|
27 | h2: [],
|
---|
28 | h3: [],
|
---|
29 | h4: [],
|
---|
30 | h5: [],
|
---|
31 | h6: [],
|
---|
32 | i: [],
|
---|
33 | img: ['src', 'srcset', 'alt', 'title', 'width', 'height'],
|
---|
34 | li: [],
|
---|
35 | ol: [],
|
---|
36 | p: [],
|
---|
37 | pre: [],
|
---|
38 | s: [],
|
---|
39 | small: [],
|
---|
40 | span: [],
|
---|
41 | sub: [],
|
---|
42 | sup: [],
|
---|
43 | strong: [],
|
---|
44 | u: [],
|
---|
45 | ul: []
|
---|
46 | }
|
---|
47 | // js-docs-end allow-list
|
---|
48 |
|
---|
49 | const uriAttributes = new Set([
|
---|
50 | 'background',
|
---|
51 | 'cite',
|
---|
52 | 'href',
|
---|
53 | 'itemtype',
|
---|
54 | 'longdesc',
|
---|
55 | 'poster',
|
---|
56 | 'src',
|
---|
57 | 'xlink:href'
|
---|
58 | ])
|
---|
59 |
|
---|
60 | /**
|
---|
61 | * A pattern that recognizes URLs that are safe wrt. XSS in URL navigation
|
---|
62 | * contexts.
|
---|
63 | *
|
---|
64 | * Shout-out to Angular https://github.com/angular/angular/blob/15.2.8/packages/core/src/sanitization/url_sanitizer.ts#L38
|
---|
65 | */
|
---|
66 | // eslint-disable-next-line unicorn/better-regex
|
---|
67 | const SAFE_URL_PATTERN = /^(?!javascript:)(?:[a-z0-9+.-]+:|[^&:/?#]*(?:[/?#]|$))/i
|
---|
68 |
|
---|
69 | const allowedAttribute = (attribute, allowedAttributeList) => {
|
---|
70 | const attributeName = attribute.nodeName.toLowerCase()
|
---|
71 |
|
---|
72 | if (allowedAttributeList.includes(attributeName)) {
|
---|
73 | if (uriAttributes.has(attributeName)) {
|
---|
74 | return Boolean(SAFE_URL_PATTERN.test(attribute.nodeValue))
|
---|
75 | }
|
---|
76 |
|
---|
77 | return true
|
---|
78 | }
|
---|
79 |
|
---|
80 | // Check if a regular expression validates the attribute.
|
---|
81 | return allowedAttributeList.filter(attributeRegex => attributeRegex instanceof RegExp)
|
---|
82 | .some(regex => regex.test(attributeName))
|
---|
83 | }
|
---|
84 |
|
---|
85 | export function sanitizeHtml(unsafeHtml, allowList, sanitizeFunction) {
|
---|
86 | if (!unsafeHtml.length) {
|
---|
87 | return unsafeHtml
|
---|
88 | }
|
---|
89 |
|
---|
90 | if (sanitizeFunction && typeof sanitizeFunction === 'function') {
|
---|
91 | return sanitizeFunction(unsafeHtml)
|
---|
92 | }
|
---|
93 |
|
---|
94 | const domParser = new window.DOMParser()
|
---|
95 | const createdDocument = domParser.parseFromString(unsafeHtml, 'text/html')
|
---|
96 | const elements = [].concat(...createdDocument.body.querySelectorAll('*'))
|
---|
97 |
|
---|
98 | for (const element of elements) {
|
---|
99 | const elementName = element.nodeName.toLowerCase()
|
---|
100 |
|
---|
101 | if (!Object.keys(allowList).includes(elementName)) {
|
---|
102 | element.remove()
|
---|
103 | continue
|
---|
104 | }
|
---|
105 |
|
---|
106 | const attributeList = [].concat(...element.attributes)
|
---|
107 | const allowedAttributes = [].concat(allowList['*'] || [], allowList[elementName] || [])
|
---|
108 |
|
---|
109 | for (const attribute of attributeList) {
|
---|
110 | if (!allowedAttribute(attribute, allowedAttributes)) {
|
---|
111 | element.removeAttribute(attribute.nodeName)
|
---|
112 | }
|
---|
113 | }
|
---|
114 | }
|
---|
115 |
|
---|
116 | return createdDocument.body.innerHTML
|
---|
117 | }
|
---|