1 | /**
|
2 | * Sanitize a tree.
|
3 | *
|
4 | * @param {Readonly<Nodes>} node
|
5 | * Unsafe tree.
|
6 | * @param {Readonly<Schema> | null | undefined} [options]
|
7 | * Configuration (default: `defaultSchema`).
|
8 | * @returns {Nodes}
|
9 | * New, safe tree.
|
10 | */
|
11 | export function sanitize(node: Readonly<Nodes>, options?: Readonly<Schema> | null | undefined): Nodes;
|
12 | export type Comment = import('hast').Comment;
|
13 | export type Doctype = import('hast').Doctype;
|
14 | export type Element = import('hast').Element;
|
15 | export type ElementContent = import('hast').ElementContent;
|
16 | export type Nodes = import('hast').Nodes;
|
17 | export type Properties = import('hast').Properties;
|
18 | export type Root = import('hast').Root;
|
19 | export type RootContent = import('hast').RootContent;
|
20 | export type Text = import('hast').Text;
|
21 | /**
|
22 | * Definition for a property.
|
23 | */
|
24 | export type PropertyDefinition = [string, ...Array<Exclude<Properties[keyof Properties], Array<any>> | RegExp>] | string;
|
25 | /**
|
26 | * Schema that defines what nodes and properties are allowed.
|
27 | *
|
28 | * The default schema is `defaultSchema`, which follows how GitHub cleans.
|
29 | * If any top-level key is missing in the given schema, the corresponding
|
30 | * value of the default schema is used.
|
31 | *
|
32 | * To extend the standard schema with a few changes, clone `defaultSchema`
|
33 | * like so:
|
34 | *
|
35 | * ```js
|
36 | * import deepmerge from 'deepmerge'
|
37 | * import {h} from 'hastscript'
|
38 | * import {defaultSchema, sanitize} from 'hast-util-sanitize'
|
39 | *
|
40 | * // This allows `className` on all elements.
|
41 | * const schema = deepmerge(defaultSchema, {attributes: {'*': ['className']}})
|
42 | *
|
43 | * const tree = sanitize(h('div', {className: ['foo']}), schema)
|
44 | *
|
45 | * // `tree` still has `className`.
|
46 | * console.log(tree)
|
47 | * // {
|
48 | * // type: 'element',
|
49 | * // tagName: 'div',
|
50 | * // properties: {className: ['foo']},
|
51 | * // children: []
|
52 | * // }
|
53 | * ```
|
54 | */
|
55 | export type Schema = {
|
56 | /**
|
57 | * Whether to allow comment nodes (default: `false`).
|
58 | *
|
59 | * For example:
|
60 | *
|
61 | * ```js
|
62 | * allowComments: true
|
63 | * ```
|
64 | */
|
65 | allowComments?: boolean | null | undefined;
|
66 | /**
|
67 | * Whether to allow doctype nodes (default: `false`).
|
68 | *
|
69 | * For example:
|
70 | *
|
71 | * ```js
|
72 | * allowDoctypes: true
|
73 | * ```
|
74 | */
|
75 | allowDoctypes?: boolean | null | undefined;
|
76 | /**
|
77 | * Map of tag names to a list of tag names which are required ancestors
|
78 | * (default: `defaultSchema.ancestors`).
|
79 | *
|
80 | * Elements with these tag names will be ignored if they occur outside of one
|
81 | * of their allowed parents.
|
82 | *
|
83 | * For example:
|
84 | *
|
85 | * ```js
|
86 | * ancestors: {
|
87 | * tbody: ['table'],
|
88 | * // …
|
89 | * tr: ['table']
|
90 | * }
|
91 | * ```
|
92 | */
|
93 | ancestors?: Record<string, Array<string>> | null | undefined;
|
94 | /**
|
95 | * Map of tag names to allowed property names (default:
|
96 | * `defaultSchema.attributes`).
|
97 | *
|
98 | * The special key `'*'` as a tag name defines property names allowed on all
|
99 | * elements.
|
100 | *
|
101 | * The special value `'data*'` as a property name can be used to allow all
|
102 | * `data` properties.
|
103 | *
|
104 | * For example:
|
105 | *
|
106 | * ```js
|
107 | * attributes: {
|
108 | * 'ariaDescribedBy', 'ariaLabel', 'ariaLabelledBy', …, 'href'
|
109 | * // …
|
110 | * '*': [
|
111 | * 'abbr',
|
112 | * 'accept',
|
113 | * 'acceptCharset',
|
114 | * // …
|
115 | * 'vAlign',
|
116 | * 'value',
|
117 | * 'width'
|
118 | * ]
|
119 | * }
|
120 | * ```
|
121 | *
|
122 | * Instead of a single string in the array, which allows any property value
|
123 | * for the field, you can use an array to allow several values.
|
124 | * For example, `input: ['type']` allows `type` set to any value on `input`s.
|
125 | * But `input: [['type', 'checkbox', 'radio']]` allows `type` when set to
|
126 | * `'checkbox'` or `'radio'`.
|
127 | *
|
128 | * You can use regexes, so for example `span: [['className', /^hljs-/]]`
|
129 | * allows any class that starts with `hljs-` on `span`s.
|
130 | *
|
131 | * When comma- or space-separated values are used (such as `className`), each
|
132 | * value in is checked individually.
|
133 | * For example, to allow certain classes on `span`s for syntax highlighting,
|
134 | * use `span: [['className', 'number', 'operator', 'token']]`.
|
135 | * This will allow `'number'`, `'operator'`, and `'token'` classes, but drop
|
136 | * others.
|
137 | */
|
138 | attributes?: Record<string, Array<PropertyDefinition>> | null | undefined;
|
139 | /**
|
140 | * List of property names that clobber (default: `defaultSchema.clobber`).
|
141 | *
|
142 | * For example:
|
143 | *
|
144 | * ```js
|
145 | * clobber: ['ariaDescribedBy', 'ariaLabelledBy', 'id', 'name']
|
146 | * ```
|
147 | */
|
148 | clobber?: Array<string> | null | undefined;
|
149 | /**
|
150 | * Prefix to use before clobbering properties (default:
|
151 | * `defaultSchema.clobberPrefix`).
|
152 | *
|
153 | * For example:
|
154 | *
|
155 | * ```js
|
156 | * clobberPrefix: 'user-content-'
|
157 | * ```
|
158 | */
|
159 | clobberPrefix?: string | null | undefined;
|
160 | /**
|
161 | * Map of *property names* to allowed protocols (default:
|
162 | * `defaultSchema.protocols`).
|
163 | *
|
164 | * This defines URLs that are always allowed to have local URLs (relative to
|
165 | * the current website, such as `this`, `#this`, `/this`, or `?this`), and
|
166 | * only allowed to have remote URLs (such as `https://example.com`) if they
|
167 | * use a known protocol.
|
168 | *
|
169 | * For example:
|
170 | *
|
171 | * ```js
|
172 | * protocols: {
|
173 | * cite: ['http', 'https'],
|
174 | * // …
|
175 | * src: ['http', 'https']
|
176 | * }
|
177 | * ```
|
178 | */
|
179 | protocols?: Record<string, Array<string> | null | undefined> | null | undefined;
|
180 | /**
|
181 | * Map of tag names to required property names with a default value
|
182 | * (default: `defaultSchema.required`).
|
183 | *
|
184 | * This defines properties that must be set.
|
185 | * If a field does not exist (after the element was made safe), these will be
|
186 | * added with the given value.
|
187 | *
|
188 | * For example:
|
189 | *
|
190 | * ```js
|
191 | * required: {
|
192 | * input: {disabled: true, type: 'checkbox'}
|
193 | * }
|
194 | * ```
|
195 | *
|
196 | * > 👉 **Note**: properties are first checked based on `schema.attributes`,
|
197 | * > then on `schema.required`.
|
198 | * > That means properties could be removed by `attributes` and then added
|
199 | * > again with `required`.
|
200 | */
|
201 | required?: Record<string, Record<string, Properties[keyof Properties]>> | null | undefined;
|
202 | /**
|
203 | * List of tag names to strip from the tree (default: `defaultSchema.strip`).
|
204 | *
|
205 | * By default, unsafe elements (those not in `schema.tagNames`) are replaced
|
206 | * by what they contain.
|
207 | * This option can drop their contents.
|
208 | *
|
209 | * For example:
|
210 | *
|
211 | * ```js
|
212 | * strip: ['script']
|
213 | * ```
|
214 | */
|
215 | strip?: Array<string> | null | undefined;
|
216 | /**
|
217 | * List of allowed tag names (default: `defaultSchema.tagNames`).
|
218 | *
|
219 | * For example:
|
220 | *
|
221 | * ```js
|
222 | * tagNames: [
|
223 | * 'a',
|
224 | * 'b',
|
225 | * // …
|
226 | * 'ul',
|
227 | * 'var'
|
228 | * ]
|
229 | * ```
|
230 | */
|
231 | tagNames?: Array<string> | null | undefined;
|
232 | };
|
233 | /**
|
234 | * Info passed around.
|
235 | */
|
236 | export type State = {
|
237 | /**
|
238 | * Schema.
|
239 | */
|
240 | schema: Readonly<Schema>;
|
241 | /**
|
242 | * Tag names of ancestors.
|
243 | */
|
244 | stack: Array<string>;
|
245 | };
|