sanitize-html 1.16.3 → 1.18.2
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/.travis.yml +5 -0
- package/README.md +98 -2
- package/dist/index.js +106 -9
- package/dist/sanitize-html.js +1878 -75
- package/dist/sanitize-html.min.js +12 -12
- package/package.json +6 -3
- package/src/index.js +68 -13
- package/test/test.js +75 -0
package/.travis.yml
ADDED
package/README.md
CHANGED
|
@@ -14,6 +14,8 @@ The syntax of poorly closed `p` and `img` elements is cleaned up.
|
|
|
14
14
|
|
|
15
15
|
`href` attributes are validated to ensure they only contain `http`, `https`, `ftp` and `mailto` URLs. Relative URLs are also allowed. Ditto for `src` attributes.
|
|
16
16
|
|
|
17
|
+
Allowing particular urls as a `src` to an iframe tag by filtering hostnames is also supported.
|
|
18
|
+
|
|
17
19
|
HTML comments are not preserved.
|
|
18
20
|
|
|
19
21
|
## Requirements
|
|
@@ -82,7 +84,8 @@ clean = sanitizeHtml(dirty, {
|
|
|
82
84
|
allowedTags: [ 'b', 'i', 'em', 'strong', 'a' ],
|
|
83
85
|
allowedAttributes: {
|
|
84
86
|
'a': [ 'href' ]
|
|
85
|
-
}
|
|
87
|
+
},
|
|
88
|
+
allowedIframeHostnames: ['www.youtube.com']
|
|
86
89
|
});
|
|
87
90
|
```
|
|
88
91
|
|
|
@@ -115,7 +118,9 @@ selfClosing: [ 'img', 'br', 'hr', 'area', 'base', 'basefont', 'input', 'link', '
|
|
|
115
118
|
// URL schemes we permit
|
|
116
119
|
allowedSchemes: [ 'http', 'https', 'ftp', 'mailto' ],
|
|
117
120
|
allowedSchemesByTag: {},
|
|
118
|
-
|
|
121
|
+
allowedSchemesAppliedToAttributes: [ 'href', 'src', 'cite' ],
|
|
122
|
+
allowProtocolRelative: true,
|
|
123
|
+
allowedIframeHostnames: ['www.youtube.com', 'player.vimeo.com']
|
|
119
124
|
```
|
|
120
125
|
|
|
121
126
|
#### "What if I want to allow all tags or all attributes?"
|
|
@@ -137,6 +142,23 @@ allowedTags: [],
|
|
|
137
142
|
allowedAttributes: []
|
|
138
143
|
```
|
|
139
144
|
|
|
145
|
+
### "What if I want to allow only specific values on some attributes?"
|
|
146
|
+
|
|
147
|
+
When configuring the attribute in `allowedAttributes` simply use an object with attribute `name` and an allowed `values` array. In the following example `sandbox="allow-forms allow-modals allow-orientation-lock allow-pointer-lock allow-popups allow-popups-to-escape-sandbox allow-scripts"` would become `sandbox="allow-popups allow-scripts"`:
|
|
148
|
+
|
|
149
|
+
```js
|
|
150
|
+
allowedAttributes: {
|
|
151
|
+
iframe: [
|
|
152
|
+
{
|
|
153
|
+
name: 'sandbox',
|
|
154
|
+
multiple: true,
|
|
155
|
+
values: ['allow-popups', 'allow-same-origin', 'allow-scripts']
|
|
156
|
+
}
|
|
157
|
+
]
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
With `multiple: true`, several allowed values may appear in the same attribute, separated by spaces. Otherwise the attribute must exactly match one and only one of the allowed values.
|
|
161
|
+
|
|
140
162
|
### Wildcards for attributes
|
|
141
163
|
|
|
142
164
|
You can use the `*` wildcard to allow all attributes with a certain prefix:
|
|
@@ -294,6 +316,61 @@ sanitizeHtml(
|
|
|
294
316
|
|
|
295
317
|
Note that the text passed to the `textFilter` method is already escaped for safe display as HTML. You may add markup and use entity escape sequences in your `textFilter`.
|
|
296
318
|
|
|
319
|
+
### Iframe Filters
|
|
320
|
+
|
|
321
|
+
If you would like to allow iframe tags but want to control the domains that are allowed through you can provide an array of hostnames that you would like to allow as iframe sources. This hostname is a property in the options object passed as an argument to the `sanitize-html` function.
|
|
322
|
+
|
|
323
|
+
This array will be checked against the html that is passed to the function and return only `src` urls that include the allowed hostnames in the object. The url in the html that is passed must be formatted correctly (valid hostname) as an embedded iframe otherwise the module will strip out the src from the iframe.
|
|
324
|
+
|
|
325
|
+
Make sure to pass a valid hostname along with the domain you wish to allow, i.e.:
|
|
326
|
+
|
|
327
|
+
```javascript
|
|
328
|
+
allowedIframeHostnames: ['www.youtube.com', 'player.vimeo.com']
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
**Remember that the `iframe` tag must be allowed as well as the `src` attribute.**
|
|
332
|
+
|
|
333
|
+
For example:
|
|
334
|
+
|
|
335
|
+
```javascript
|
|
336
|
+
clean = sanitizeHtml('<p><iframe src="https://www.youtube.com/embed/nykIhs12345"></iframe><p>', {
|
|
337
|
+
allowedTags: [ 'p', 'em', 'strong', 'iframe' ],
|
|
338
|
+
allowedClasses: {
|
|
339
|
+
'p': [ 'fancy', 'simple' ],
|
|
340
|
+
'iframe': ['src']
|
|
341
|
+
},
|
|
342
|
+
allowedIframeHostnames: ['www.youtube.com', 'player.vimeo.com']
|
|
343
|
+
});
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
will pass through as safe whereas:
|
|
347
|
+
|
|
348
|
+
```javascript
|
|
349
|
+
clean = sanitizeHtml('<p><iframe src="https://www.youtube.net/embed/nykIhs12345"></iframe><p>', {
|
|
350
|
+
allowedTags: [ 'p', 'em', 'strong', 'iframe' ],
|
|
351
|
+
allowedClasses: {
|
|
352
|
+
'p': [ 'fancy', 'simple' ],
|
|
353
|
+
'iframe': ['src']
|
|
354
|
+
},
|
|
355
|
+
allowedIframeHostnames: ['www.youtube.com', 'player.vimeo.com']
|
|
356
|
+
});
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
or
|
|
360
|
+
|
|
361
|
+
```javascript
|
|
362
|
+
clean = sanitizeHtml('<p><iframe src="https://www.vimeo/video/12345"></iframe><p>', {
|
|
363
|
+
allowedTags: [ 'p', 'em', 'strong', 'iframe' ],
|
|
364
|
+
allowedClasses: {
|
|
365
|
+
'p': [ 'fancy', 'simple' ],
|
|
366
|
+
'iframe': ['src']
|
|
367
|
+
},
|
|
368
|
+
allowedIframeHostnames: ['www.youtube.com', 'player.vimeo.com']
|
|
369
|
+
});
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
will return an empty iframe tag.
|
|
373
|
+
|
|
297
374
|
### Allowed CSS Classes
|
|
298
375
|
|
|
299
376
|
If you wish to allow specific CSS classes on a particular element, you can do so with the `allowedClasses` option. Any other CSS classes are discarded.
|
|
@@ -397,6 +474,25 @@ The content still gets escaped properly, with the exception of the `script` and
|
|
|
397
474
|
|
|
398
475
|
## Changelog
|
|
399
476
|
|
|
477
|
+
1.18.2:
|
|
478
|
+
|
|
479
|
+
* Travis tests passing.
|
|
480
|
+
* Fixed another case issue — and instituted Travis CI testing so this doesn't happen again. Sorry for the hassle.
|
|
481
|
+
|
|
482
|
+
1.18.1:
|
|
483
|
+
|
|
484
|
+
* A file was required with incorrect case, breaking the library on case sensitive filesystems such as Linux. Fixed.
|
|
485
|
+
|
|
486
|
+
1.18.0:
|
|
487
|
+
|
|
488
|
+
* The new `allowedSchemesAppliedToAttributes` option. This determines which attributes are validated as URLs, replacing the old hardcoded list of `src` and `href` only. The default list now includes `cite`. Thanks to ml-dublin for this contribution.
|
|
489
|
+
|
|
490
|
+
* It is now easy to configure a specific list of allowed values for an attribute. When configuring `allowedAttributes`, rather than listing an attribute name, simply list an object with an attribute `name` property and an allowed `values` array property. You can also add `multiple: true` to allow multiple space-separated allowed values in the attribute, otherwise the attribute must match one and only one of the allowed values. Thanks again to ml-dublin for this contribution.
|
|
491
|
+
|
|
492
|
+
* Fixed a bug in the npm test procedure.
|
|
493
|
+
|
|
494
|
+
1.17.0: the new `allowedIframeHostnames` option. If present, this must be an array, and only iframe `src` URLs hostnames (complete hostnames; domain name matches are not enough) that appear on this list are allowed. You must also configure `hostname` as an allowed attribute for `iframe`. Thanks to Ryan Verys for this contribution.
|
|
495
|
+
|
|
400
496
|
1.16.3: don't throw away the browserified versions before publishing them. `prepare` is not a good place to `make clean`, it runs after `prepublish`.
|
|
401
497
|
|
|
402
498
|
1.16.2: `sanitize-html` is now compiled with `babel`. An npm `prepublish` script takes care of this at `npm publish` time, so the latest code should always be compiled to operate all the way back to ES5 browsers and earlier versions of Node. Thanks to Ayushya Jaiswal.
|
package/dist/index.js
CHANGED
|
@@ -5,8 +5,11 @@ var extend = require('xtend');
|
|
|
5
5
|
var quoteRegexp = require('lodash.escaperegexp');
|
|
6
6
|
var cloneDeep = require('lodash.clonedeep');
|
|
7
7
|
var mergeWith = require('lodash.mergewith');
|
|
8
|
+
var isString = require('lodash.isstring');
|
|
9
|
+
var isPlainObject = require('lodash.isplainobject');
|
|
8
10
|
var srcset = require('srcset');
|
|
9
11
|
var postcss = require('postcss');
|
|
12
|
+
var url = require('url');
|
|
10
13
|
|
|
11
14
|
function each(obj, cb) {
|
|
12
15
|
if (obj) Object.keys(obj).forEach(function (key) {
|
|
@@ -92,11 +95,11 @@ function sanitizeHtml(html, options, _recursing) {
|
|
|
92
95
|
each(options.allowedAttributes, function (attributes, tag) {
|
|
93
96
|
allowedAttributesMap[tag] = [];
|
|
94
97
|
var globRegex = [];
|
|
95
|
-
attributes.forEach(function (
|
|
96
|
-
if (
|
|
97
|
-
globRegex.push(quoteRegexp(
|
|
98
|
+
attributes.forEach(function (obj) {
|
|
99
|
+
if (isString(obj) && obj.indexOf('*') >= 0) {
|
|
100
|
+
globRegex.push(quoteRegexp(obj).replace(/\\\*/g, '.*'));
|
|
98
101
|
} else {
|
|
99
|
-
allowedAttributesMap[tag].push(
|
|
102
|
+
allowedAttributesMap[tag].push(obj);
|
|
100
103
|
}
|
|
101
104
|
});
|
|
102
105
|
allowedAttributesGlobMap[tag] = new RegExp('^(' + globRegex.join('|') + ')$');
|
|
@@ -197,17 +200,111 @@ function sanitizeHtml(html, options, _recursing) {
|
|
|
197
200
|
return;
|
|
198
201
|
}
|
|
199
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;
|
|
200
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)) {
|
|
201
|
-
|
|
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) {
|
|
202
277
|
if (naughtyHref(name, value)) {
|
|
203
278
|
delete frame.attribs[a];
|
|
204
279
|
return;
|
|
205
280
|
}
|
|
206
281
|
}
|
|
207
|
-
|
|
282
|
+
if (name === 'iframe' && a === 'src') {
|
|
283
|
+
//Check if value contains proper hostname prefix
|
|
284
|
+
if (value.substring(0, 2) === '//') {
|
|
285
|
+
var prefix = 'https:';
|
|
286
|
+
value = prefix.concat(value);
|
|
287
|
+
}
|
|
288
|
+
try {
|
|
289
|
+
parsed = url.parse(value);
|
|
290
|
+
if (options.allowedIframeHostnames) {
|
|
291
|
+
var whitelistedHostnames = options.allowedIframeHostnames.find(function (hostname) {
|
|
292
|
+
return hostname === parsed.hostname;
|
|
293
|
+
});
|
|
294
|
+
if (!whitelistedHostnames) {
|
|
295
|
+
delete frame.attribs[a];
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
} catch (e) {
|
|
300
|
+
// Unparseable iframe src
|
|
301
|
+
delete frame.attribs[a];
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
208
305
|
if (a === 'srcset') {
|
|
209
306
|
try {
|
|
210
|
-
|
|
307
|
+
parsed = srcset.parse(value);
|
|
211
308
|
each(parsed, function (value) {
|
|
212
309
|
if (naughtyHref('srcset', value.url)) {
|
|
213
310
|
value.evil = true;
|
|
@@ -231,7 +328,6 @@ function sanitizeHtml(html, options, _recursing) {
|
|
|
231
328
|
return;
|
|
232
329
|
}
|
|
233
330
|
}
|
|
234
|
-
|
|
235
331
|
if (a === 'class') {
|
|
236
332
|
value = filterClasses(value, allowedClassesMap[name]);
|
|
237
333
|
if (!value.length) {
|
|
@@ -486,7 +582,7 @@ var htmlParserDefaults = {
|
|
|
486
582
|
decodeEntities: true
|
|
487
583
|
};
|
|
488
584
|
sanitizeHtml.defaults = {
|
|
489
|
-
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'],
|
|
585
|
+
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'],
|
|
490
586
|
allowedAttributes: {
|
|
491
587
|
a: ['href', 'name', 'target'],
|
|
492
588
|
// We don't currently allow img itself by default, but this
|
|
@@ -499,6 +595,7 @@ sanitizeHtml.defaults = {
|
|
|
499
595
|
// URL schemes we permit
|
|
500
596
|
allowedSchemes: ['http', 'https', 'ftp', 'mailto'],
|
|
501
597
|
allowedSchemesByTag: {},
|
|
598
|
+
allowedSchemesAppliedToAttributes: ['href', 'src', 'cite'],
|
|
502
599
|
allowProtocolRelative: true
|
|
503
600
|
};
|
|
504
601
|
|