333
schangxiang@126.com
2025-09-19 18966e02fb573c7e2bb0c6426ed792b38b910940
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
'use strict';
 
const urlparse = require('url').parse;
const isSafeDomain = require('../utils').isSafeDomain;
const xss = require('xss');
const BUILD_IN_ON_TAG_ATTR = Symbol('buildInOnTagAttr');
const utils = require('../utils');
 
// default rule: https://github.com/leizongmin/js-xss/blob/master/lib/default.js
// add domain filter based on xss module
// custom options http://jsxss.com/zh/options.html
// eg: support a tag,filter attributes except for title : whiteList: {a: ['title']}
module.exports = function shtml(val) {
  if (typeof val !== 'string') return val;
 
  const securityOptions = this.ctx.securityOptions || {};
  const shtmlConfig = utils.merge(this.app.config.helper.shtml, securityOptions.shtml);
  const domainWhiteList = this.app.config.security.domainWhiteList;
  const app = this.app;
  // filter href and src attribute if not in domain white list
  if (!shtmlConfig[BUILD_IN_ON_TAG_ATTR]) {
    shtmlConfig[BUILD_IN_ON_TAG_ATTR] = function(tag, name, value, isWhiteAttr) {
      if (isWhiteAttr && (name === 'href' || name === 'src')) {
        if (!value) {
          return;
        }
 
        value = String(value);
 
        if (value[0] === '/' || value[0] === '#') {
          return;
        }
 
        const hostname = urlparse(value).hostname;
        if (!hostname) {
          return;
        }
 
        // If we don't have our hostname in the app.security.domainWhiteList,
        // Just check for `shtmlConfig.domainWhiteList` and `ctx.whiteList`.
        if (!isSafeDomain(hostname, domainWhiteList)) {
          // Check for `shtmlConfig.domainWhiteList` first (duplicated now)
          if (shtmlConfig.domainWhiteList && shtmlConfig.domainWhiteList.length !== 0) {
            app.deprecate('[egg-security] `config.helper.shtml.domainWhiteList` has been deprecate. Please use `config.security.domainWhiteList` instead.');
            shtmlConfig.domainWhiteList = shtmlConfig.domainWhiteList.map(domain => domain.toLowerCase());
            if (!isSafeDomain(hostname, shtmlConfig.domainWhiteList)) {
              return '';
            }
          } else {
            return '';
          }
        }
      }
    };
 
    // avoid overriding user configuration 'onTagAttr'
    if (shtmlConfig.onTagAttr) {
      const original = shtmlConfig.onTagAttr;
      shtmlConfig.onTagAttr = function() {
        const result = original.apply(this, arguments);
        if (result !== undefined) {
          return result;
        }
        return shtmlConfig[BUILD_IN_ON_TAG_ATTR].apply(this, arguments);
 
      };
    } else {
      shtmlConfig.onTagAttr = shtmlConfig[BUILD_IN_ON_TAG_ATTR];
    }
  }
 
  return xss(val, shtmlConfig);
};