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 ADDED
@@ -0,0 +1,5 @@
1
+ language: node_js
2
+ node_js:
3
+ - "stable"
4
+ - "lts/*"
5
+
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
- allowProtocolRelative: true
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 (name) {
96
- if (name.indexOf('*') >= 0) {
97
- globRegex.push(quoteRegexp(name).replace(/\\\*/g, '.*'));
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(name);
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
- if (a === 'href' || a === 'src') {
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
- var parsed = srcset.parse(value);
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