mathpix-markdown-it 1.0.71 → 1.0.72

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,779 @@
1
+ var __values = (this && this.__values) || function(o) {
2
+ var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0;
3
+ if (m) return m.call(o);
4
+ if (o && typeof o.length === "number") return {
5
+ next: function () {
6
+ if (o && i >= o.length) o = void 0;
7
+ return { value: o && o[i++], done: !o };
8
+ }
9
+ };
10
+ throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined.");
11
+ };
12
+ var htmlparser = require('htmlparser2');
13
+ var escapeStringRegexp = require('escape-string-regexp');
14
+ var isPlainObject = require('is-plain-object').isPlainObject;
15
+ var deepmerge = require('deepmerge');
16
+ var parseSrcset = require('parse-srcset');
17
+ var postcssParse = require('postcss').parse;
18
+ // Tags that can conceivably represent stand-alone media.
19
+ var mediaTags = [
20
+ 'img', 'audio', 'video', 'picture', 'svg',
21
+ 'object', 'map', 'iframe', 'embed'
22
+ ];
23
+ // Tags that are inherently vulnerable to being used in XSS attacks.
24
+ var vulnerableTags = ['script', 'style'];
25
+ function each(obj, cb) {
26
+ if (obj) {
27
+ Object.keys(obj).forEach(function (key) {
28
+ cb(obj[key], key);
29
+ });
30
+ }
31
+ }
32
+ // Avoid false positives with .__proto__, .hasOwnProperty, etc.
33
+ function has(obj, key) {
34
+ return ({}).hasOwnProperty.call(obj, key);
35
+ }
36
+ // Returns those elements of `a` for which `cb(a)` returns truthy
37
+ function filter(a, cb) {
38
+ var n = [];
39
+ each(a, function (v) {
40
+ if (cb(v)) {
41
+ n.push(v);
42
+ }
43
+ });
44
+ return n;
45
+ }
46
+ function isEmptyObject(obj) {
47
+ for (var key in obj) {
48
+ if (has(obj, key)) {
49
+ return false;
50
+ }
51
+ }
52
+ return true;
53
+ }
54
+ function stringifySrcset(parsedSrcset) {
55
+ return parsedSrcset.map(function (part) {
56
+ if (!part.url) {
57
+ throw new Error('URL missing');
58
+ }
59
+ return (part.url +
60
+ (part.w ? " " + part.w + "w" : '') +
61
+ (part.h ? " " + part.h + "h" : '') +
62
+ (part.d ? " " + part.d + "x" : ''));
63
+ }).join(', ');
64
+ }
65
+ module.exports = sanitizeHtml;
66
+ // A valid attribute name.
67
+ // We use a tolerant definition based on the set of strings defined by
68
+ // html.spec.whatwg.org/multipage/parsing.html#before-attribute-name-state
69
+ // and html.spec.whatwg.org/multipage/parsing.html#attribute-name-state .
70
+ // The characters accepted are ones which can be appended to the attribute
71
+ // name buffer without triggering a parse error:
72
+ // * unexpected-equals-sign-before-attribute-name
73
+ // * unexpected-null-character
74
+ // * unexpected-character-in-attribute-name
75
+ // We exclude the empty string because it's impossible to get to the after
76
+ // attribute name state with an empty attribute name buffer.
77
+ var VALID_HTML_ATTRIBUTE_NAME = /^[^\0\t\n\f\r /<=>]+$/;
78
+ // Ignore the _recursing flag; it's there for recursive
79
+ // invocation as a guard against this exploit:
80
+ // https://github.com/fb55/htmlparser2/issues/105
81
+ function sanitizeHtml(html, options, _recursing) {
82
+ if (html == null) {
83
+ return '';
84
+ }
85
+ var result = '';
86
+ // Used for hot swapping the result variable with an empty string in order to "capture" the text written to it.
87
+ var tempResult = '';
88
+ function Frame(tag, attribs) {
89
+ var that = this;
90
+ this.tag = tag;
91
+ this.attribs = attribs || {};
92
+ this.tagPosition = result.length;
93
+ this.text = ''; // Node inner text
94
+ this.mediaChildren = [];
95
+ this.updateParentNodeText = function () {
96
+ if (stack.length) {
97
+ var parentFrame = stack[stack.length - 1];
98
+ parentFrame.text += that.text;
99
+ }
100
+ };
101
+ this.updateParentNodeMediaChildren = function () {
102
+ if (stack.length && mediaTags.includes(this.tag)) {
103
+ var parentFrame = stack[stack.length - 1];
104
+ parentFrame.mediaChildren.push(this.tag);
105
+ }
106
+ };
107
+ }
108
+ options = Object.assign({}, sanitizeHtml.defaults, options);
109
+ options.parser = Object.assign({}, htmlParserDefaults, options.parser);
110
+ // vulnerableTags
111
+ vulnerableTags.forEach(function (tag) {
112
+ if (options.allowedTags && options.allowedTags.indexOf(tag) > -1 &&
113
+ !options.allowVulnerableTags) {
114
+ console.warn("\n\n\u26A0\uFE0F Your `allowedTags` option includes, `" + tag + "`, which is inherently\nvulnerable to XSS attacks. Please remove it from `allowedTags`.\nOr, to disable this warning, add the `allowVulnerableTags` option\nand ensure you are accounting for this risk.\n\n");
115
+ }
116
+ });
117
+ // Tags that contain something other than HTML, or where discarding
118
+ // the text when the tag is disallowed makes sense for other reasons.
119
+ // If we are not allowing these tags, we should drop their content too.
120
+ // For other tags you would drop the tag but keep its content.
121
+ var nonTextTagsArray = options.nonTextTags || [
122
+ 'script',
123
+ 'style',
124
+ 'textarea',
125
+ 'option'
126
+ ];
127
+ var allowedAttributesMap;
128
+ var allowedAttributesGlobMap;
129
+ if (options.allowedAttributes) {
130
+ allowedAttributesMap = {};
131
+ allowedAttributesGlobMap = {};
132
+ each(options.allowedAttributes, function (attributes, tag) {
133
+ allowedAttributesMap[tag] = [];
134
+ var globRegex = [];
135
+ attributes.forEach(function (obj) {
136
+ if (typeof obj === 'string' && obj.indexOf('*') >= 0) {
137
+ globRegex.push(escapeStringRegexp(obj).replace(/\\\*/g, '.*'));
138
+ }
139
+ else {
140
+ allowedAttributesMap[tag].push(obj);
141
+ }
142
+ });
143
+ if (globRegex.length) {
144
+ allowedAttributesGlobMap[tag] = new RegExp('^(' + globRegex.join('|') + ')$');
145
+ }
146
+ });
147
+ }
148
+ var allowedClassesMap = {};
149
+ var allowedClassesGlobMap = {};
150
+ var allowedClassesRegexMap = {};
151
+ each(options.allowedClasses, function (classes, tag) {
152
+ // Implicitly allows the class attribute
153
+ if (allowedAttributesMap) {
154
+ if (!has(allowedAttributesMap, tag)) {
155
+ allowedAttributesMap[tag] = [];
156
+ }
157
+ allowedAttributesMap[tag].push('class');
158
+ }
159
+ allowedClassesMap[tag] = [];
160
+ allowedClassesRegexMap[tag] = [];
161
+ var globRegex = [];
162
+ classes.forEach(function (obj) {
163
+ if (typeof obj === 'string' && obj.indexOf('*') >= 0) {
164
+ globRegex.push(escapeStringRegexp(obj).replace(/\\\*/g, '.*'));
165
+ }
166
+ else if (obj instanceof RegExp) {
167
+ allowedClassesRegexMap[tag].push(obj);
168
+ }
169
+ else {
170
+ allowedClassesMap[tag].push(obj);
171
+ }
172
+ });
173
+ if (globRegex.length) {
174
+ allowedClassesGlobMap[tag] = new RegExp('^(' + globRegex.join('|') + ')$');
175
+ }
176
+ });
177
+ var transformTagsMap = {};
178
+ var transformTagsAll;
179
+ each(options.transformTags, function (transform, tag) {
180
+ var transFun;
181
+ if (typeof transform === 'function') {
182
+ transFun = transform;
183
+ }
184
+ else if (typeof transform === 'string') {
185
+ transFun = sanitizeHtml.simpleTransform(transform);
186
+ }
187
+ if (tag === '*') {
188
+ transformTagsAll = transFun;
189
+ }
190
+ else {
191
+ transformTagsMap[tag] = transFun;
192
+ }
193
+ });
194
+ var depth;
195
+ var stack;
196
+ var skipMap;
197
+ var transformMap;
198
+ var skipText;
199
+ var skipTextDepth;
200
+ var addedText = false;
201
+ initializeState();
202
+ var parser = new htmlparser.Parser({
203
+ onopentag: function (name, attribs) {
204
+ // If `enforceHtmlBoundary` is `true` and this has found the opening
205
+ // `html` tag, reset the state.
206
+ if (options.enforceHtmlBoundary && name === 'html') {
207
+ initializeState();
208
+ }
209
+ if (skipText) {
210
+ skipTextDepth++;
211
+ return;
212
+ }
213
+ var frame = new Frame(name, attribs);
214
+ stack.push(frame);
215
+ var skip = false;
216
+ var hasText = !!frame.text;
217
+ var transformedTag;
218
+ if (has(transformTagsMap, name)) {
219
+ transformedTag = transformTagsMap[name](name, attribs);
220
+ frame.attribs = attribs = transformedTag.attribs;
221
+ if (transformedTag.text !== undefined) {
222
+ frame.innerText = transformedTag.text;
223
+ }
224
+ if (name !== transformedTag.tagName) {
225
+ frame.name = name = transformedTag.tagName;
226
+ transformMap[depth] = transformedTag.tagName;
227
+ }
228
+ }
229
+ if (transformTagsAll) {
230
+ transformedTag = transformTagsAll(name, attribs);
231
+ frame.attribs = attribs = transformedTag.attribs;
232
+ if (name !== transformedTag.tagName) {
233
+ frame.name = name = transformedTag.tagName;
234
+ transformMap[depth] = transformedTag.tagName;
235
+ }
236
+ }
237
+ if ((options.allowedTags && options.allowedTags.indexOf(name) === -1) || (options.disallowedTagsMode === 'recursiveEscape' && !isEmptyObject(skipMap)) || (options.nestingLimit != null && depth >= options.nestingLimit)) {
238
+ skip = true;
239
+ skipMap[depth] = true;
240
+ if (options.disallowedTagsMode === 'discard') {
241
+ if (nonTextTagsArray.indexOf(name) !== -1) {
242
+ skipText = true;
243
+ skipTextDepth = 1;
244
+ }
245
+ }
246
+ skipMap[depth] = true;
247
+ }
248
+ depth++;
249
+ if (skip) {
250
+ if (options.disallowedTagsMode === 'discard') {
251
+ // We want the contents but not this tag
252
+ return;
253
+ }
254
+ tempResult = result;
255
+ result = '';
256
+ }
257
+ result += '<' + name;
258
+ if (name === 'script') {
259
+ if (options.allowedScriptHostnames || options.allowedScriptDomains) {
260
+ frame.innerText = '';
261
+ }
262
+ }
263
+ if (!allowedAttributesMap || has(allowedAttributesMap, name) || allowedAttributesMap['*']) {
264
+ each(attribs, function (value, a) {
265
+ var e_1, _a, e_2, _b;
266
+ if (!VALID_HTML_ATTRIBUTE_NAME.test(a)) {
267
+ // This prevents part of an attribute name in the output from being
268
+ // interpreted as the end of an attribute, or end of a tag.
269
+ delete frame.attribs[a];
270
+ return;
271
+ }
272
+ var parsed;
273
+ // check allowedAttributesMap for the element and attribute and modify the value
274
+ // as necessary if there are specific values defined.
275
+ var passedAllowedAttributesMapCheck = false;
276
+ if (!allowedAttributesMap ||
277
+ (has(allowedAttributesMap, name) && allowedAttributesMap[name].indexOf(a) !== -1) ||
278
+ (allowedAttributesMap['*'] && allowedAttributesMap['*'].indexOf(a) !== -1) ||
279
+ (has(allowedAttributesGlobMap, name) && allowedAttributesGlobMap[name].test(a)) ||
280
+ (allowedAttributesGlobMap['*'] && allowedAttributesGlobMap['*'].test(a))) {
281
+ passedAllowedAttributesMapCheck = true;
282
+ }
283
+ else if (allowedAttributesMap && allowedAttributesMap[name]) {
284
+ try {
285
+ for (var _c = __values(allowedAttributesMap[name]), _d = _c.next(); !_d.done; _d = _c.next()) {
286
+ var o = _d.value;
287
+ if (isPlainObject(o) && o.name && (o.name === a)) {
288
+ passedAllowedAttributesMapCheck = true;
289
+ var newValue = '';
290
+ if (o.multiple === true) {
291
+ // verify the values that are allowed
292
+ var splitStrArray = value.split(' ');
293
+ try {
294
+ for (var splitStrArray_1 = (e_2 = void 0, __values(splitStrArray)), splitStrArray_1_1 = splitStrArray_1.next(); !splitStrArray_1_1.done; splitStrArray_1_1 = splitStrArray_1.next()) {
295
+ var s = splitStrArray_1_1.value;
296
+ if (o.values.indexOf(s) !== -1) {
297
+ if (newValue === '') {
298
+ newValue = s;
299
+ }
300
+ else {
301
+ newValue += ' ' + s;
302
+ }
303
+ }
304
+ }
305
+ }
306
+ catch (e_2_1) { e_2 = { error: e_2_1 }; }
307
+ finally {
308
+ try {
309
+ if (splitStrArray_1_1 && !splitStrArray_1_1.done && (_b = splitStrArray_1.return)) _b.call(splitStrArray_1);
310
+ }
311
+ finally { if (e_2) throw e_2.error; }
312
+ }
313
+ }
314
+ else if (o.values.indexOf(value) >= 0) {
315
+ // verified an allowed value matches the entire attribute value
316
+ newValue = value;
317
+ }
318
+ value = newValue;
319
+ }
320
+ }
321
+ }
322
+ catch (e_1_1) { e_1 = { error: e_1_1 }; }
323
+ finally {
324
+ try {
325
+ if (_d && !_d.done && (_a = _c.return)) _a.call(_c);
326
+ }
327
+ finally { if (e_1) throw e_1.error; }
328
+ }
329
+ }
330
+ if (passedAllowedAttributesMapCheck) {
331
+ if (options.allowedSchemesAppliedToAttributes.indexOf(a) !== -1) {
332
+ if (naughtyHref(name, value)) {
333
+ delete frame.attribs[a];
334
+ return;
335
+ }
336
+ }
337
+ if (name === 'script' && a === 'src') {
338
+ var allowed = true;
339
+ try {
340
+ var parsed_1 = new URL(value);
341
+ if (options.allowedScriptHostnames || options.allowedScriptDomains) {
342
+ var allowedHostname = (options.allowedScriptHostnames || []).find(function (hostname) {
343
+ return hostname === parsed_1.hostname;
344
+ });
345
+ var allowedDomain = (options.allowedScriptDomains || []).find(function (domain) {
346
+ return parsed_1.hostname === domain || parsed_1.hostname.endsWith("." + domain);
347
+ });
348
+ allowed = allowedHostname || allowedDomain;
349
+ }
350
+ }
351
+ catch (e) {
352
+ allowed = false;
353
+ }
354
+ if (!allowed) {
355
+ delete frame.attribs[a];
356
+ return;
357
+ }
358
+ }
359
+ if (name === 'iframe' && a === 'src') {
360
+ var allowed = true;
361
+ try {
362
+ // Chrome accepts \ as a substitute for / in the // at the
363
+ // start of a URL, so rewrite accordingly to prevent exploit.
364
+ // Also drop any whitespace at that point in the URL
365
+ value = value.replace(/^(\w+:)?\s*[\\/]\s*[\\/]/, '$1//');
366
+ if (value.startsWith('relative:')) {
367
+ // An attempt to exploit our workaround for base URLs being
368
+ // mandatory for relative URL validation in the WHATWG
369
+ // URL parser, reject it
370
+ throw new Error('relative: exploit attempt');
371
+ }
372
+ // naughtyHref is in charge of whether protocol relative URLs
373
+ // are cool. Here we are concerned just with allowed hostnames and
374
+ // whether to allow relative URLs.
375
+ //
376
+ // Build a placeholder "base URL" against which any reasonable
377
+ // relative URL may be parsed successfully
378
+ var base = 'relative://relative-site';
379
+ for (var i = 0; (i < 100); i++) {
380
+ base += "/" + i;
381
+ }
382
+ var parsed_2 = new URL(value, base);
383
+ var isRelativeUrl = parsed_2 && parsed_2.hostname === 'relative-site' && parsed_2.protocol === 'relative:';
384
+ if (isRelativeUrl) {
385
+ // default value of allowIframeRelativeUrls is true
386
+ // unless allowedIframeHostnames or allowedIframeDomains specified
387
+ allowed = has(options, 'allowIframeRelativeUrls')
388
+ ? options.allowIframeRelativeUrls
389
+ : (!options.allowedIframeHostnames && !options.allowedIframeDomains);
390
+ }
391
+ else if (options.allowedIframeHostnames || options.allowedIframeDomains) {
392
+ var allowedHostname = (options.allowedIframeHostnames || []).find(function (hostname) {
393
+ return hostname === parsed_2.hostname;
394
+ });
395
+ var allowedDomain = (options.allowedIframeDomains || []).find(function (domain) {
396
+ return parsed_2.hostname === domain || parsed_2.hostname.endsWith("." + domain);
397
+ });
398
+ allowed = allowedHostname || allowedDomain;
399
+ }
400
+ }
401
+ catch (e) {
402
+ // Unparseable iframe src
403
+ allowed = false;
404
+ }
405
+ if (!allowed) {
406
+ delete frame.attribs[a];
407
+ return;
408
+ }
409
+ }
410
+ if (a === 'srcset') {
411
+ try {
412
+ parsed = parseSrcset(value);
413
+ parsed.forEach(function (value) {
414
+ if (naughtyHref('srcset', value.url)) {
415
+ value.evil = true;
416
+ }
417
+ });
418
+ parsed = filter(parsed, function (v) {
419
+ return !v.evil;
420
+ });
421
+ if (!parsed.length) {
422
+ delete frame.attribs[a];
423
+ return;
424
+ }
425
+ else {
426
+ value = stringifySrcset(filter(parsed, function (v) {
427
+ return !v.evil;
428
+ }));
429
+ frame.attribs[a] = value;
430
+ }
431
+ }
432
+ catch (e) {
433
+ // Unparseable srcset
434
+ delete frame.attribs[a];
435
+ return;
436
+ }
437
+ }
438
+ if (a === 'class') {
439
+ var allowedSpecificClasses = allowedClassesMap[name];
440
+ var allowedWildcardClasses = allowedClassesMap['*'];
441
+ var allowedSpecificClassesGlob = allowedClassesGlobMap[name];
442
+ var allowedSpecificClassesRegex = allowedClassesRegexMap[name];
443
+ var allowedWildcardClassesGlob = allowedClassesGlobMap['*'];
444
+ var allowedClassesGlobs = [
445
+ allowedSpecificClassesGlob,
446
+ allowedWildcardClassesGlob
447
+ ]
448
+ .concat(allowedSpecificClassesRegex)
449
+ .filter(function (t) {
450
+ return t;
451
+ });
452
+ if (allowedSpecificClasses && allowedWildcardClasses) {
453
+ value = filterClasses(value, deepmerge(allowedSpecificClasses, allowedWildcardClasses), allowedClassesGlobs);
454
+ }
455
+ else {
456
+ value = filterClasses(value, allowedSpecificClasses || allowedWildcardClasses, allowedClassesGlobs);
457
+ }
458
+ if (!value.length) {
459
+ delete frame.attribs[a];
460
+ return;
461
+ }
462
+ }
463
+ if (a === 'style') {
464
+ try {
465
+ var abstractSyntaxTree = postcssParse(name + ' {' + value + '}');
466
+ var filteredAST = filterCss(abstractSyntaxTree, options.allowedStyles);
467
+ value = stringifyStyleAttributes(filteredAST);
468
+ if (value.length === 0) {
469
+ delete frame.attribs[a];
470
+ return;
471
+ }
472
+ }
473
+ catch (e) {
474
+ delete frame.attribs[a];
475
+ return;
476
+ }
477
+ }
478
+ result += ' ' + a;
479
+ if (value && value.length) {
480
+ result += '="' + escapeHtml(value, true) + '"';
481
+ }
482
+ }
483
+ else {
484
+ delete frame.attribs[a];
485
+ }
486
+ });
487
+ }
488
+ if (options.selfClosing.indexOf(name) !== -1) {
489
+ result += ' />';
490
+ }
491
+ else {
492
+ result += '>';
493
+ if (frame.innerText && !hasText && !options.textFilter) {
494
+ result += escapeHtml(frame.innerText);
495
+ addedText = true;
496
+ }
497
+ }
498
+ if (skip) {
499
+ result = tempResult + escapeHtml(result);
500
+ tempResult = '';
501
+ }
502
+ },
503
+ ontext: function (text) {
504
+ if (skipText) {
505
+ return;
506
+ }
507
+ var lastFrame = stack[stack.length - 1];
508
+ var tag;
509
+ if (lastFrame) {
510
+ tag = lastFrame.tag;
511
+ // If inner text was set by transform function then let's use it
512
+ text = lastFrame.innerText !== undefined ? lastFrame.innerText : text;
513
+ }
514
+ if (options.disallowedTagsMode === 'discard' && ((tag === 'script') || (tag === 'style'))) {
515
+ // htmlparser2 gives us these as-is. Escaping them ruins the content. Allowing
516
+ // script tags is, by definition, game over for XSS protection, so if that's
517
+ // your concern, don't allow them. The same is essentially true for style tags
518
+ // which have their own collection of XSS vectors.
519
+ result += text;
520
+ }
521
+ else {
522
+ var escaped = escapeHtml(text, false);
523
+ if (options.textFilter && !addedText) {
524
+ result += options.textFilter(escaped, tag);
525
+ }
526
+ else if (!addedText) {
527
+ result += escaped;
528
+ }
529
+ }
530
+ if (stack.length) {
531
+ var frame = stack[stack.length - 1];
532
+ frame.text += text;
533
+ }
534
+ },
535
+ onclosetag: function (name) {
536
+ if (skipText) {
537
+ skipTextDepth--;
538
+ if (!skipTextDepth) {
539
+ skipText = false;
540
+ }
541
+ else {
542
+ return;
543
+ }
544
+ }
545
+ var frame = stack.pop();
546
+ if (!frame) {
547
+ // Do not crash on bad markup
548
+ return;
549
+ }
550
+ skipText = options.enforceHtmlBoundary ? name === 'html' : false;
551
+ depth--;
552
+ var skip = skipMap[depth];
553
+ if (skip) {
554
+ delete skipMap[depth];
555
+ if (options.disallowedTagsMode === 'discard') {
556
+ frame.updateParentNodeText();
557
+ return;
558
+ }
559
+ tempResult = result;
560
+ result = '';
561
+ }
562
+ if (transformMap[depth]) {
563
+ name = transformMap[depth];
564
+ delete transformMap[depth];
565
+ }
566
+ if (options.exclusiveFilter && options.exclusiveFilter(frame)) {
567
+ result = result.substr(0, frame.tagPosition);
568
+ return;
569
+ }
570
+ frame.updateParentNodeMediaChildren();
571
+ frame.updateParentNodeText();
572
+ if (options.selfClosing.indexOf(name) !== -1) {
573
+ // Already output />
574
+ if (skip) {
575
+ result = tempResult;
576
+ tempResult = '';
577
+ }
578
+ return;
579
+ }
580
+ if (!options.skipCloseTag) {
581
+ result += '</' + name + '>';
582
+ }
583
+ if (skip) {
584
+ result = tempResult + escapeHtml(result);
585
+ tempResult = '';
586
+ }
587
+ addedText = false;
588
+ }
589
+ }, options.parser);
590
+ parser.write(html);
591
+ parser.end();
592
+ return result;
593
+ function initializeState() {
594
+ result = '';
595
+ depth = 0;
596
+ stack = [];
597
+ skipMap = {};
598
+ transformMap = {};
599
+ skipText = false;
600
+ skipTextDepth = 0;
601
+ }
602
+ function escapeHtml(s, quote) {
603
+ if (typeof (s) !== 'string') {
604
+ s = s + '';
605
+ }
606
+ if (options.parser.decodeEntities) {
607
+ s = s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
608
+ if (quote) {
609
+ s = s.replace(/"/g, '&quot;');
610
+ }
611
+ }
612
+ // TODO: this is inadequate because it will pass `&0;`. This approach
613
+ // will not work, each & must be considered with regard to whether it
614
+ // is followed by a 100% syntactically valid entity or not, and escaped
615
+ // if it is not. If this bothers you, don't set parser.decodeEntities
616
+ // to false. (The default is true.)
617
+ s = s.replace(/&(?![a-zA-Z0-9#]{1,20};)/g, '&amp;') // Match ampersands not part of existing HTML entity
618
+ .replace(/</g, '&lt;')
619
+ .replace(/>/g, '&gt;');
620
+ if (quote) {
621
+ s = s.replace(/"/g, '&quot;');
622
+ }
623
+ return s;
624
+ }
625
+ function naughtyHref(name, href) {
626
+ // Browsers ignore character codes of 32 (space) and below in a surprising
627
+ // number of situations. Start reading here:
628
+ // https://www.owasp.org/index.php/XSS_Filter_Evasion_Cheat_Sheet#Embedded_tab
629
+ // eslint-disable-next-line no-control-regex
630
+ href = href.replace(/[\x00-\x20]+/g, '');
631
+ // Clobber any comments in URLs, which the browser might
632
+ // interpret inside an XML data island, allowing
633
+ // a javascript: URL to be snuck through
634
+ href = href.replace(/<!--.*?-->/g, '');
635
+ // Case insensitive so we don't get faked out by JAVASCRIPT #1
636
+ // Allow more characters after the first so we don't get faked
637
+ // out by certain schemes browsers accept
638
+ var matches = href.match(/^([a-zA-Z][a-zA-Z0-9.\-+]*):/);
639
+ if (!matches) {
640
+ // Protocol-relative URL starting with any combination of '/' and '\'
641
+ if (href.match(/^[/\\]{2}/)) {
642
+ return !options.allowProtocolRelative;
643
+ }
644
+ // No scheme
645
+ return false;
646
+ }
647
+ var scheme = matches[1].toLowerCase();
648
+ if (has(options.allowedSchemesByTag, name)) {
649
+ return options.allowedSchemesByTag[name].indexOf(scheme) === -1;
650
+ }
651
+ return !options.allowedSchemes || options.allowedSchemes.indexOf(scheme) === -1;
652
+ }
653
+ /**
654
+ * Filters user input css properties by allowlisted regex attributes.
655
+ * Modifies the abstractSyntaxTree object.
656
+ *
657
+ * @param {object} abstractSyntaxTree - Object representation of CSS attributes.
658
+ * @property {array[Declaration]} abstractSyntaxTree.nodes[0] - Each object cointains prop and value key, i.e { prop: 'color', value: 'red' }.
659
+ * @param {object} allowedStyles - Keys are properties (i.e color), value is list of permitted regex rules (i.e /green/i).
660
+ * @return {object} - The modified tree.
661
+ */
662
+ function filterCss(abstractSyntaxTree, allowedStyles) {
663
+ if (!allowedStyles) {
664
+ return abstractSyntaxTree;
665
+ }
666
+ var astRules = abstractSyntaxTree.nodes[0];
667
+ var selectedRule;
668
+ // Merge global and tag-specific styles into new AST.
669
+ if (allowedStyles[astRules.selector] && allowedStyles['*']) {
670
+ selectedRule = deepmerge(allowedStyles[astRules.selector], allowedStyles['*']);
671
+ }
672
+ else {
673
+ selectedRule = allowedStyles[astRules.selector] || allowedStyles['*'];
674
+ }
675
+ if (selectedRule) {
676
+ abstractSyntaxTree.nodes[0].nodes = astRules.nodes.reduce(filterDeclarations(selectedRule), []);
677
+ }
678
+ return abstractSyntaxTree;
679
+ }
680
+ /**
681
+ * Extracts the style attributes from an AbstractSyntaxTree and formats those
682
+ * values in the inline style attribute format.
683
+ *
684
+ * @param {AbstractSyntaxTree} filteredAST
685
+ * @return {string} - Example: "color:yellow;text-align:center !important;font-family:helvetica;"
686
+ */
687
+ function stringifyStyleAttributes(filteredAST) {
688
+ return filteredAST.nodes[0].nodes
689
+ .reduce(function (extractedAttributes, attrObject) {
690
+ extractedAttributes.push(attrObject.prop + ":" + attrObject.value + (attrObject.important ? ' !important' : ''));
691
+ return extractedAttributes;
692
+ }, [])
693
+ .join(';');
694
+ }
695
+ /**
696
+ * Filters the existing attributes for the given property. Discards any attributes
697
+ * which don't match the allowlist.
698
+ *
699
+ * @param {object} selectedRule - Example: { color: red, font-family: helvetica }
700
+ * @param {array} allowedDeclarationsList - List of declarations which pass the allowlist.
701
+ * @param {object} attributeObject - Object representing the current css property.
702
+ * @property {string} attributeObject.type - Typically 'declaration'.
703
+ * @property {string} attributeObject.prop - The CSS property, i.e 'color'.
704
+ * @property {string} attributeObject.value - The corresponding value to the css property, i.e 'red'.
705
+ * @return {function} - When used in Array.reduce, will return an array of Declaration objects
706
+ */
707
+ function filterDeclarations(selectedRule) {
708
+ return function (allowedDeclarationsList, attributeObject) {
709
+ // If this property is allowlisted...
710
+ if (has(selectedRule, attributeObject.prop)) {
711
+ var matchesRegex = selectedRule[attributeObject.prop].some(function (regularExpression) {
712
+ return regularExpression.test(attributeObject.value);
713
+ });
714
+ if (matchesRegex) {
715
+ allowedDeclarationsList.push(attributeObject);
716
+ }
717
+ }
718
+ return allowedDeclarationsList;
719
+ };
720
+ }
721
+ function filterClasses(classes, allowed, allowedGlobs) {
722
+ if (!allowed) {
723
+ // The class attribute is allowed without filtering on this tag
724
+ return classes;
725
+ }
726
+ classes = classes.split(/\s+/);
727
+ return classes.filter(function (clss) {
728
+ return allowed.indexOf(clss) !== -1 || allowedGlobs.some(function (glob) {
729
+ return glob.test(clss);
730
+ });
731
+ }).join(' ');
732
+ }
733
+ }
734
+ // Defaults are accessible to you so that you can use them as a starting point
735
+ // programmatically if you wish
736
+ var htmlParserDefaults = {
737
+ decodeEntities: true
738
+ };
739
+ sanitizeHtml.defaults = {
740
+ allowedTags: ['h3', 'h4', 'h5', 'h6', 'blockquote', 'p', 'a', 'ul', 'ol',
741
+ 'nl', 'li', 'b', 'i', 'strong', 'em', 'strike', 'abbr', 'code', 'hr', 'br', 'div',
742
+ 'table', 'thead', 'caption', 'tbody', 'tr', 'th', 'td', 'pre', 'iframe'],
743
+ disallowedTagsMode: 'discard',
744
+ allowedAttributes: {
745
+ a: ['href', 'name', 'target'],
746
+ // We don't currently allow img itself by default, but
747
+ // these attributes would make sense if we did.
748
+ img: ['src']
749
+ },
750
+ // Lots of these won't come up by default because we don't allow them
751
+ selfClosing: ['img', 'br', 'hr', 'area', 'base', 'basefont', 'input', 'link', 'meta'],
752
+ // URL schemes we permit
753
+ allowedSchemes: ['http', 'https', 'ftp', 'mailto'],
754
+ allowedSchemesByTag: {},
755
+ allowedSchemesAppliedToAttributes: ['href', 'src', 'cite'],
756
+ allowProtocolRelative: true,
757
+ enforceHtmlBoundary: false,
758
+ skipCloseTag: false
759
+ };
760
+ sanitizeHtml.simpleTransform = function (newTagName, newAttribs, merge) {
761
+ merge = (merge === undefined) ? true : merge;
762
+ newAttribs = newAttribs || {};
763
+ return function (tagName, attribs) {
764
+ var attrib;
765
+ if (merge) {
766
+ for (attrib in newAttribs) {
767
+ attribs[attrib] = newAttribs[attrib];
768
+ }
769
+ }
770
+ else {
771
+ attribs = newAttribs;
772
+ }
773
+ return {
774
+ tagName: newTagName,
775
+ attribs: attribs
776
+ };
777
+ };
778
+ };
779
+ //# sourceMappingURL=sanitize-html.js.map