sanitize-html 1.20.1 → 1.22.1

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.
package/package.json CHANGED
@@ -1,16 +1,15 @@
1
1
  {
2
2
  "name": "sanitize-html",
3
- "version": "1.20.1",
3
+ "version": "1.22.1",
4
4
  "description": "Clean up user-submitted HTML, preserving whitelisted elements and whitelisted attributes on a per-element basis",
5
- "main": "dist/index.js",
5
+ "main": "dist/sanitize-html.js",
6
6
  "files": [
7
7
  "dist/"
8
8
  ],
9
9
  "scripts": {
10
- "prepare": "true",
11
- "build": "make clean && make all && npm run prepare && browserify dist/index.js > dist/sanitize-html.js --standalone 'sanitizeHtml'",
10
+ "build": "mkdir -p dist && browserify src/index.js > dist/sanitize-html-es2015.js --standalone 'sanitizeHtml' && babel dist/sanitize-html-es2015.js --out-file dist/sanitize-html.js --presets=@babel/preset-env",
12
11
  "minify": "npm run build && uglifyjs dist/sanitize-html.js > dist/sanitize-html.min.js",
13
- "prepublishOnly": "make clean && npm run minify",
12
+ "prepublishOnly": "npm run minify",
14
13
  "test": "npm run prepublishOnly && mocha test/test.js"
15
14
  },
16
15
  "repository": {
@@ -29,22 +28,23 @@
29
28
  "license": "MIT",
30
29
  "dependencies": {
31
30
  "chalk": "^2.4.1",
32
- "htmlparser2": "^3.10.0",
31
+ "htmlparser2": "^4.1.0",
33
32
  "lodash.clonedeep": "^4.5.0",
34
33
  "lodash.escaperegexp": "^4.1.2",
35
34
  "lodash.isplainobject": "^4.0.6",
36
35
  "lodash.isstring": "^4.0.1",
37
- "lodash.mergewith": "^4.6.1",
38
- "postcss": "^7.0.5",
39
- "srcset": "^1.0.0",
36
+ "lodash.mergewith": "^4.6.2",
37
+ "postcss": "^7.0.27",
38
+ "srcset": "^2.0.1",
40
39
  "xtend": "^4.0.1"
41
40
  },
42
41
  "devDependencies": {
43
- "babel-cli": "^6.26.0",
44
- "babel-preset-env": "^1.7.0",
42
+ "@babel/cli": "^7.8.4",
43
+ "@babel/core": "^7.8.4",
44
+ "@babel/preset-env": "^7.8.4",
45
45
  "babelify": "^10.0.0",
46
46
  "browserify": "^16.2.3",
47
47
  "mocha": "^5.2.0",
48
- "uglify-js": "^3.4.9"
48
+ "uglify-js": "^3.8.0"
49
49
  }
50
50
  }
package/dist/index.js DELETED
@@ -1,638 +0,0 @@
1
- 'use strict';
2
-
3
- var htmlparser = require('htmlparser2');
4
- var extend = require('xtend');
5
- var quoteRegexp = require('lodash.escaperegexp');
6
- var cloneDeep = require('lodash.clonedeep');
7
- var mergeWith = require('lodash.mergewith');
8
- var isString = require('lodash.isstring');
9
- var isPlainObject = require('lodash.isplainobject');
10
- var srcset = require('srcset');
11
- var postcss = require('postcss');
12
- var url = require('url');
13
-
14
- function each(obj, cb) {
15
- if (obj) Object.keys(obj).forEach(function (key) {
16
- cb(obj[key], key);
17
- });
18
- }
19
-
20
- // Avoid false positives with .__proto__, .hasOwnProperty, etc.
21
- function has(obj, key) {
22
- return {}.hasOwnProperty.call(obj, key);
23
- }
24
-
25
- // Returns those elements of `a` for which `cb(a)` returns truthy
26
- function filter(a, cb) {
27
- var n = [];
28
- each(a, function (v) {
29
- if (cb(v)) {
30
- n.push(v);
31
- }
32
- });
33
- return n;
34
- }
35
-
36
- module.exports = sanitizeHtml;
37
-
38
- // A valid attribute name.
39
- // We use a tolerant definition based on the set of strings defined by
40
- // html.spec.whatwg.org/multipage/parsing.html#before-attribute-name-state
41
- // and html.spec.whatwg.org/multipage/parsing.html#attribute-name-state .
42
- // The characters accepted are ones which can be appended to the attribute
43
- // name buffer without triggering a parse error:
44
- // * unexpected-equals-sign-before-attribute-name
45
- // * unexpected-null-character
46
- // * unexpected-character-in-attribute-name
47
- // We exclude the empty string because it's impossible to get to the after
48
- // attribute name state with an empty attribute name buffer.
49
- var VALID_HTML_ATTRIBUTE_NAME = /^[^\0\t\n\f\r /<=>]+$/;
50
-
51
- // Ignore the _recursing flag; it's there for recursive
52
- // invocation as a guard against this exploit:
53
- // https://github.com/fb55/htmlparser2/issues/105
54
-
55
- function sanitizeHtml(html, options, _recursing) {
56
- var result = '';
57
-
58
- function Frame(tag, attribs) {
59
- var that = this;
60
- this.tag = tag;
61
- this.attribs = attribs || {};
62
- this.tagPosition = result.length;
63
- this.text = ''; // Node inner text
64
-
65
- this.updateParentNodeText = function () {
66
- if (stack.length) {
67
- var parentFrame = stack[stack.length - 1];
68
- parentFrame.text += that.text;
69
- }
70
- };
71
- }
72
-
73
- if (!options) {
74
- options = sanitizeHtml.defaults;
75
- options.parser = htmlParserDefaults;
76
- } else {
77
- options = extend(sanitizeHtml.defaults, options);
78
- if (options.parser) {
79
- options.parser = extend(htmlParserDefaults, options.parser);
80
- } else {
81
- options.parser = htmlParserDefaults;
82
- }
83
- }
84
-
85
- // Tags that contain something other than HTML, or where discarding
86
- // the text when the tag is disallowed makes sense for other reasons.
87
- // If we are not allowing these tags, we should drop their content too.
88
- // For other tags you would drop the tag but keep its content.
89
- var nonTextTagsArray = options.nonTextTags || ['script', 'style', 'textarea'];
90
- var allowedAttributesMap;
91
- var allowedAttributesGlobMap;
92
- if (options.allowedAttributes) {
93
- allowedAttributesMap = {};
94
- allowedAttributesGlobMap = {};
95
- each(options.allowedAttributes, function (attributes, tag) {
96
- allowedAttributesMap[tag] = [];
97
- var globRegex = [];
98
- attributes.forEach(function (obj) {
99
- if (isString(obj) && obj.indexOf('*') >= 0) {
100
- globRegex.push(quoteRegexp(obj).replace(/\\\*/g, '.*'));
101
- } else {
102
- allowedAttributesMap[tag].push(obj);
103
- }
104
- });
105
- allowedAttributesGlobMap[tag] = new RegExp('^(' + globRegex.join('|') + ')$');
106
- });
107
- }
108
- var allowedClassesMap = {};
109
- each(options.allowedClasses, function (classes, tag) {
110
- // Implicitly allows the class attribute
111
- if (allowedAttributesMap) {
112
- if (!has(allowedAttributesMap, tag)) {
113
- allowedAttributesMap[tag] = [];
114
- }
115
- allowedAttributesMap[tag].push('class');
116
- }
117
-
118
- allowedClassesMap[tag] = classes;
119
- });
120
-
121
- var transformTagsMap = {};
122
- var transformTagsAll;
123
- each(options.transformTags, function (transform, tag) {
124
- var transFun;
125
- if (typeof transform === 'function') {
126
- transFun = transform;
127
- } else if (typeof transform === "string") {
128
- transFun = sanitizeHtml.simpleTransform(transform);
129
- }
130
- if (tag === '*') {
131
- transformTagsAll = transFun;
132
- } else {
133
- transformTagsMap[tag] = transFun;
134
- }
135
- });
136
-
137
- var depth = 0;
138
- var stack = [];
139
- var skipMap = {};
140
- var transformMap = {};
141
- var skipText = false;
142
- var skipTextDepth = 0;
143
-
144
- var parser = new htmlparser.Parser({
145
- onopentag: function onopentag(name, attribs) {
146
- if (skipText) {
147
- skipTextDepth++;
148
- return;
149
- }
150
- var frame = new Frame(name, attribs);
151
- stack.push(frame);
152
-
153
- var skip = false;
154
- var hasText = frame.text ? true : false;
155
- var transformedTag;
156
- if (has(transformTagsMap, name)) {
157
- transformedTag = transformTagsMap[name](name, attribs);
158
-
159
- frame.attribs = attribs = transformedTag.attribs;
160
-
161
- if (transformedTag.text !== undefined) {
162
- frame.innerText = transformedTag.text;
163
- }
164
-
165
- if (name !== transformedTag.tagName) {
166
- frame.name = name = transformedTag.tagName;
167
- transformMap[depth] = transformedTag.tagName;
168
- }
169
- }
170
- if (transformTagsAll) {
171
- transformedTag = transformTagsAll(name, attribs);
172
-
173
- frame.attribs = attribs = transformedTag.attribs;
174
- if (name !== transformedTag.tagName) {
175
- frame.name = name = transformedTag.tagName;
176
- transformMap[depth] = transformedTag.tagName;
177
- }
178
- }
179
-
180
- if (options.allowedTags && options.allowedTags.indexOf(name) === -1) {
181
- skip = true;
182
- if (nonTextTagsArray.indexOf(name) !== -1) {
183
- skipText = true;
184
- skipTextDepth = 1;
185
- }
186
- skipMap[depth] = true;
187
- }
188
- depth++;
189
- if (skip) {
190
- // We want the contents but not this tag
191
- return;
192
- }
193
- result += '<' + name;
194
- if (!allowedAttributesMap || has(allowedAttributesMap, name) || allowedAttributesMap['*']) {
195
- each(attribs, function (value, a) {
196
- if (!VALID_HTML_ATTRIBUTE_NAME.test(a)) {
197
- // This prevents part of an attribute name in the output from being
198
- // interpreted as the end of an attribute, or end of a tag.
199
- delete frame.attribs[a];
200
- return;
201
- }
202
- var parsed;
203
- // check allowedAttributesMap for the element and attribute and modify the value
204
- // as necessary if there are specific values defined.
205
- var passedAllowedAttributesMapCheck = false;
206
- if (!allowedAttributesMap || has(allowedAttributesMap, name) && allowedAttributesMap[name].indexOf(a) !== -1 || allowedAttributesMap['*'] && allowedAttributesMap['*'].indexOf(a) !== -1 || has(allowedAttributesGlobMap, name) && allowedAttributesGlobMap[name].test(a) || allowedAttributesGlobMap['*'] && allowedAttributesGlobMap['*'].test(a)) {
207
- passedAllowedAttributesMapCheck = true;
208
- } else if (allowedAttributesMap && allowedAttributesMap[name]) {
209
- var _iteratorNormalCompletion = true;
210
- var _didIteratorError = false;
211
- var _iteratorError = undefined;
212
-
213
- try {
214
- for (var _iterator = allowedAttributesMap[name][Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
215
- var o = _step.value;
216
-
217
- if (isPlainObject(o) && o.name && o.name === a) {
218
- passedAllowedAttributesMapCheck = true;
219
- var newValue = '';
220
- if (o.multiple === true) {
221
- // verify the values that are allowed
222
- var splitStrArray = value.split(' ');
223
- var _iteratorNormalCompletion2 = true;
224
- var _didIteratorError2 = false;
225
- var _iteratorError2 = undefined;
226
-
227
- try {
228
- for (var _iterator2 = splitStrArray[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) {
229
- var s = _step2.value;
230
-
231
- if (o.values.indexOf(s) !== -1) {
232
- if (newValue === '') {
233
- newValue = s;
234
- } else {
235
- newValue += ' ' + s;
236
- }
237
- }
238
- }
239
- } catch (err) {
240
- _didIteratorError2 = true;
241
- _iteratorError2 = err;
242
- } finally {
243
- try {
244
- if (!_iteratorNormalCompletion2 && _iterator2.return) {
245
- _iterator2.return();
246
- }
247
- } finally {
248
- if (_didIteratorError2) {
249
- throw _iteratorError2;
250
- }
251
- }
252
- }
253
- } else if (o.values.indexOf(value) >= 0) {
254
- // verified an allowed value matches the entire attribute value
255
- newValue = value;
256
- }
257
- value = newValue;
258
- }
259
- }
260
- } catch (err) {
261
- _didIteratorError = true;
262
- _iteratorError = err;
263
- } finally {
264
- try {
265
- if (!_iteratorNormalCompletion && _iterator.return) {
266
- _iterator.return();
267
- }
268
- } finally {
269
- if (_didIteratorError) {
270
- throw _iteratorError;
271
- }
272
- }
273
- }
274
- }
275
- if (passedAllowedAttributesMapCheck) {
276
- if (options.allowedSchemesAppliedToAttributes.indexOf(a) !== -1) {
277
- if (naughtyHref(name, value)) {
278
- delete frame.attribs[a];
279
- return;
280
- }
281
- }
282
- if (name === 'iframe' && a === 'src') {
283
- var allowed = true;
284
- try {
285
- // naughtyHref is in charge of whether protocol relative URLs
286
- // are cool. We should just accept them
287
- parsed = url.parse(value, false, true);
288
- var isRelativeUrl = parsed && parsed.host === null && parsed.protocol === null;
289
- if (isRelativeUrl) {
290
- // default value of allowIframeRelativeUrls is true unless allowIframeHostnames specified
291
- allowed = has(options, "allowIframeRelativeUrls") ? options.allowIframeRelativeUrls : !options.allowedIframeHostnames;
292
- } else if (options.allowedIframeHostnames) {
293
- allowed = options.allowedIframeHostnames.find(function (hostname) {
294
- return hostname === parsed.hostname;
295
- });
296
- }
297
- } catch (e) {
298
- // Unparseable iframe src
299
- allowed = false;
300
- }
301
- if (!allowed) {
302
- delete frame.attribs[a];
303
- return;
304
- }
305
- }
306
- if (a === 'srcset') {
307
- try {
308
- parsed = srcset.parse(value);
309
- each(parsed, function (value) {
310
- if (naughtyHref('srcset', value.url)) {
311
- value.evil = true;
312
- }
313
- });
314
- parsed = filter(parsed, function (v) {
315
- return !v.evil;
316
- });
317
- if (!parsed.length) {
318
- delete frame.attribs[a];
319
- return;
320
- } else {
321
- value = srcset.stringify(filter(parsed, function (v) {
322
- return !v.evil;
323
- }));
324
- frame.attribs[a] = value;
325
- }
326
- } catch (e) {
327
- // Unparseable srcset
328
- delete frame.attribs[a];
329
- return;
330
- }
331
- }
332
- if (a === 'class') {
333
- value = filterClasses(value, allowedClassesMap[name]);
334
- if (!value.length) {
335
- delete frame.attribs[a];
336
- return;
337
- }
338
- }
339
- if (a === 'style') {
340
- try {
341
- var abstractSyntaxTree = postcss.parse(name + " {" + value + "}");
342
- var filteredAST = filterCss(abstractSyntaxTree, options.allowedStyles);
343
-
344
- value = stringifyStyleAttributes(filteredAST);
345
-
346
- if (value.length === 0) {
347
- delete frame.attribs[a];
348
- return;
349
- }
350
- } catch (e) {
351
- delete frame.attribs[a];
352
- return;
353
- }
354
- }
355
- result += ' ' + a;
356
- if (value.length) {
357
- result += '="' + escapeHtml(value, true) + '"';
358
- }
359
- } else {
360
- delete frame.attribs[a];
361
- }
362
- });
363
- }
364
- if (options.selfClosing.indexOf(name) !== -1) {
365
- result += " />";
366
- } else {
367
- result += ">";
368
- if (frame.innerText && !hasText && !options.textFilter) {
369
- result += frame.innerText;
370
- }
371
- }
372
- },
373
- ontext: function ontext(text) {
374
- if (skipText) {
375
- return;
376
- }
377
- var lastFrame = stack[stack.length - 1];
378
- var tag;
379
-
380
- if (lastFrame) {
381
- tag = lastFrame.tag;
382
- // If inner text was set by transform function then let's use it
383
- text = lastFrame.innerText !== undefined ? lastFrame.innerText : text;
384
- }
385
-
386
- if (tag === 'script' || tag === 'style') {
387
- // htmlparser2 gives us these as-is. Escaping them ruins the content. Allowing
388
- // script tags is, by definition, game over for XSS protection, so if that's
389
- // your concern, don't allow them. The same is essentially true for style tags
390
- // which have their own collection of XSS vectors.
391
- result += text;
392
- } else {
393
- var escaped = escapeHtml(text, false);
394
- if (options.textFilter) {
395
- result += options.textFilter(escaped);
396
- } else {
397
- result += escaped;
398
- }
399
- }
400
- if (stack.length) {
401
- var frame = stack[stack.length - 1];
402
- frame.text += text;
403
- }
404
- },
405
- onclosetag: function onclosetag(name) {
406
-
407
- if (skipText) {
408
- skipTextDepth--;
409
- if (!skipTextDepth) {
410
- skipText = false;
411
- } else {
412
- return;
413
- }
414
- }
415
-
416
- var frame = stack.pop();
417
- if (!frame) {
418
- // Do not crash on bad markup
419
- return;
420
- }
421
- skipText = false;
422
- depth--;
423
- if (skipMap[depth]) {
424
- delete skipMap[depth];
425
- frame.updateParentNodeText();
426
- return;
427
- }
428
-
429
- if (transformMap[depth]) {
430
- name = transformMap[depth];
431
- delete transformMap[depth];
432
- }
433
-
434
- if (options.exclusiveFilter && options.exclusiveFilter(frame)) {
435
- result = result.substr(0, frame.tagPosition);
436
- return;
437
- }
438
-
439
- frame.updateParentNodeText();
440
-
441
- if (options.selfClosing.indexOf(name) !== -1) {
442
- // Already output />
443
- return;
444
- }
445
-
446
- result += "</" + name + ">";
447
- }
448
- }, options.parser);
449
- parser.write(html);
450
- parser.end();
451
-
452
- return result;
453
-
454
- function escapeHtml(s, quote) {
455
- if (typeof s !== 'string') {
456
- s = s + '';
457
- }
458
- if (options.parser.decodeEntities) {
459
- s = s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/\>/g, '&gt;');
460
- if (quote) {
461
- s = s.replace(/\"/g, '&quot;');
462
- }
463
- }
464
- // TODO: this is inadequate because it will pass `&0;`. This approach
465
- // will not work, each & must be considered with regard to whether it
466
- // is followed by a 100% syntactically valid entity or not, and escaped
467
- // if it is not. If this bothers you, don't set parser.decodeEntities
468
- // to false. (The default is true.)
469
- s = s.replace(/&(?![a-zA-Z0-9#]{1,20};)/g, '&amp;') // Match ampersands not part of existing HTML entity
470
- .replace(/</g, '&lt;').replace(/\>/g, '&gt;');
471
- if (quote) {
472
- s = s.replace(/\"/g, '&quot;');
473
- }
474
- return s;
475
- }
476
-
477
- function naughtyHref(name, href) {
478
- // Browsers ignore character codes of 32 (space) and below in a surprising
479
- // number of situations. Start reading here:
480
- // https://www.owasp.org/index.php/XSS_Filter_Evasion_Cheat_Sheet#Embedded_tab
481
- href = href.replace(/[\x00-\x20]+/g, '');
482
- // Clobber any comments in URLs, which the browser might
483
- // interpret inside an XML data island, allowing
484
- // a javascript: URL to be snuck through
485
- href = href.replace(/<\!\-\-.*?\-\-\>/g, '');
486
- // Case insensitive so we don't get faked out by JAVASCRIPT #1
487
- var matches = href.match(/^([a-zA-Z]+)\:/);
488
- if (!matches) {
489
- // Protocol-relative URL starting with any combination of '/' and '\'
490
- if (href.match(/^[\/\\]{2}/)) {
491
- return !options.allowProtocolRelative;
492
- }
493
-
494
- // No scheme
495
- return false;
496
- }
497
- var scheme = matches[1].toLowerCase();
498
-
499
- if (has(options.allowedSchemesByTag, name)) {
500
- return options.allowedSchemesByTag[name].indexOf(scheme) === -1;
501
- }
502
-
503
- return !options.allowedSchemes || options.allowedSchemes.indexOf(scheme) === -1;
504
- }
505
-
506
- /**
507
- * Filters user input css properties by whitelisted regex attributes.
508
- *
509
- * @param {object} abstractSyntaxTree - Object representation of CSS attributes.
510
- * @property {array[Declaration]} abstractSyntaxTree.nodes[0] - Each object cointains prop and value key, i.e { prop: 'color', value: 'red' }.
511
- * @param {object} allowedStyles - Keys are properties (i.e color), value is list of permitted regex rules (i.e /green/i).
512
- * @return {object} - Abstract Syntax Tree with filtered style attributes.
513
- */
514
- function filterCss(abstractSyntaxTree, allowedStyles) {
515
- if (!allowedStyles) {
516
- return abstractSyntaxTree;
517
- }
518
-
519
- var filteredAST = cloneDeep(abstractSyntaxTree);
520
- var astRules = abstractSyntaxTree.nodes[0];
521
- var selectedRule;
522
-
523
- // Merge global and tag-specific styles into new AST.
524
- if (allowedStyles[astRules.selector] && allowedStyles['*']) {
525
- selectedRule = mergeWith(cloneDeep(allowedStyles[astRules.selector]), allowedStyles['*'], function (objValue, srcValue) {
526
- if (Array.isArray(objValue)) {
527
- return objValue.concat(srcValue);
528
- }
529
- });
530
- } else {
531
- selectedRule = allowedStyles[astRules.selector] || allowedStyles['*'];
532
- }
533
-
534
- if (selectedRule) {
535
- filteredAST.nodes[0].nodes = astRules.nodes.reduce(filterDeclarations(selectedRule), []);
536
- }
537
-
538
- return filteredAST;
539
- }
540
-
541
- /**
542
- * Extracts the style attribues from an AbstractSyntaxTree and formats those
543
- * values in the inline style attribute format.
544
- *
545
- * @param {AbstractSyntaxTree} filteredAST
546
- * @return {string} - Example: "color:yellow;text-align:center;font-family:helvetica;"
547
- */
548
- function stringifyStyleAttributes(filteredAST) {
549
- return filteredAST.nodes[0].nodes.reduce(function (extractedAttributes, attributeObject) {
550
- extractedAttributes.push(attributeObject.prop + ':' + attributeObject.value);
551
- return extractedAttributes;
552
- }, []).join(';');
553
- }
554
-
555
- /**
556
- * Filters the existing attributes for the given property. Discards any attributes
557
- * which don't match the whitelist.
558
- *
559
- * @param {object} selectedRule - Example: { color: red, font-family: helvetica }
560
- * @param {array} allowedDeclarationsList - List of declarations which pass whitelisting.
561
- * @param {object} attributeObject - Object representing the current css property.
562
- * @property {string} attributeObject.type - Typically 'declaration'.
563
- * @property {string} attributeObject.prop - The CSS property, i.e 'color'.
564
- * @property {string} attributeObject.value - The corresponding value to the css property, i.e 'red'.
565
- * @return {function} - When used in Array.reduce, will return an array of Declaration objects
566
- */
567
- function filterDeclarations(selectedRule) {
568
- return function (allowedDeclarationsList, attributeObject) {
569
- // If this property is whitelisted...
570
- if (selectedRule.hasOwnProperty(attributeObject.prop)) {
571
- var matchesRegex = selectedRule[attributeObject.prop].some(function (regularExpression) {
572
- return regularExpression.test(attributeObject.value);
573
- });
574
-
575
- if (matchesRegex) {
576
- allowedDeclarationsList.push(attributeObject);
577
- }
578
- }
579
- return allowedDeclarationsList;
580
- };
581
- }
582
-
583
- function filterClasses(classes, allowed) {
584
- if (!allowed) {
585
- // The class attribute is allowed without filtering on this tag
586
- return classes;
587
- }
588
- classes = classes.split(/\s+/);
589
- return classes.filter(function (clss) {
590
- return allowed.indexOf(clss) !== -1;
591
- }).join(' ');
592
- }
593
- }
594
-
595
- // Defaults are accessible to you so that you can use them as a starting point
596
- // programmatically if you wish
597
-
598
- var htmlParserDefaults = {
599
- decodeEntities: true
600
- };
601
- sanitizeHtml.defaults = {
602
- allowedTags: ['h3', 'h4', 'h5', 'h6', 'blockquote', 'p', 'a', 'ul', 'ol', 'nl', 'li', 'b', 'i', 'strong', 'em', 'strike', 'code', 'hr', 'br', 'div', 'table', 'thead', 'caption', 'tbody', 'tr', 'th', 'td', 'pre', 'iframe'],
603
- allowedAttributes: {
604
- a: ['href', 'name', 'target'],
605
- // We don't currently allow img itself by default, but this
606
- // would make sense if we did. You could add srcset here,
607
- // and if you do the URL is checked for safety
608
- img: ['src']
609
- },
610
- // Lots of these won't come up by default because we don't allow them
611
- selfClosing: ['img', 'br', 'hr', 'area', 'base', 'basefont', 'input', 'link', 'meta'],
612
- // URL schemes we permit
613
- allowedSchemes: ['http', 'https', 'ftp', 'mailto'],
614
- allowedSchemesByTag: {},
615
- allowedSchemesAppliedToAttributes: ['href', 'src', 'cite'],
616
- allowProtocolRelative: true
617
- };
618
-
619
- sanitizeHtml.simpleTransform = function (newTagName, newAttribs, merge) {
620
- merge = merge === undefined ? true : merge;
621
- newAttribs = newAttribs || {};
622
-
623
- return function (tagName, attribs) {
624
- var attrib;
625
- if (merge) {
626
- for (attrib in newAttribs) {
627
- attribs[attrib] = newAttribs[attrib];
628
- }
629
- } else {
630
- attribs = newAttribs;
631
- }
632
-
633
- return {
634
- tagName: newTagName,
635
- attribs: attribs
636
- };
637
- };
638
- };