schangxiang@126.com
2025-09-19 0821aa23eabe557c0d9ef5dbe6989c68be35d1fe
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
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
'use strict';
 
const normalize = require('path').normalize;
const IP = require('ip');
const matcher = require('matcher');
 
/**
 * Check whether a domain is in the safe domain white list or not.
 * @param {String} domain The inputted domain.
 * @param {Array<string>} domain_white_list The white list for domain.
 * @return {Boolean} If the `domain` is in the white list, return true; otherwise false.
 */
exports.isSafeDomain = function isSafeDomain(domain, domain_white_list) {
  // domain must be string, otherwise return false
  if (typeof domain !== 'string') return false;
  // Ignore case sensitive first
  domain = domain.toLowerCase();
  // add prefix `.`, because all domains in white list start with `.`
  const hostname = '.' + domain;
 
  return domain_white_list.some(rule => {
 
    // Check whether we've got '*' as a wild character symbol
    if (rule.includes('*')) {
      return matcher.isMatch(domain, rule);
    }
    // If domain is an absolute path such as `http://...`
    // We can directly check whether it directly equals to `domain`
    // And we don't need to cope with `endWith`.
    return domain === rule || hostname.endsWith(rule);
  });
};
 
exports.isSafePath = function isSafePath(path, ctx) {
  path = '.' + path;
  if (path.indexOf('%') !== -1) {
    try {
      path = decodeURIComponent(path);
    } catch (e) {
      if (ctx.app.config.env === 'local' || ctx.app.config.env === 'unittest') {
        // not under production environment, output log
        ctx.coreLogger.warn('[egg-security: dta global block] : decode file path %s failed.', path);
      }
    }
  }
  const normalizePath = normalize(path);
  return !(normalizePath.startsWith('../') || normalizePath.startsWith('..\\'));
};
 
exports.checkIfIgnore = function checkIfIgnore(opts, ctx) {
  // check opts.enable first
  if (!opts.enable) return true;
  return !opts.matching(ctx);
};
 
const IP_RE = /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/;
const topDomains = {};
[ '.net.cn', '.gov.cn', '.org.cn', '.com.cn' ].forEach(function(item) {
  topDomains[item] = 2 - item.split('.').length;
});
 
exports.getCookieDomain = function getCookieDomain(hostname) {
  if (IP_RE.test(hostname)) {
    return hostname;
  }
  // app.test.domain.com => .test.domain.com
  // app.stable.domain.com => .domain.com
  // app.domain.com => .domain.com
  // domain=.domain.com;
  const splits = hostname.split('.');
  let index = -2;
 
  // only when `*.test.*.com` set `.test.*.com`
  if (splits.length >= 4 && splits[splits.length - 3] === 'test') {
    index = -3;
  }
  let domain = getDomain(splits, index);
  if (topDomains[domain]) {
    domain = getDomain(splits, index + topDomains[domain]);
  }
  return domain;
};
 
function getDomain(arr, index) {
  return '.' + arr.slice(index).join('.');
}
 
exports.merge = function merge(origin, opts) {
  if (!opts) return origin;
  const res = {};
 
  const originKeys = Object.keys(origin);
  for (let i = 0; i < originKeys.length; i++) {
    const key = originKeys[i];
    res[key] = origin[key];
  }
 
  const keys = Object.keys(opts);
  for (let i = 0; i < keys.length; i++) {
    const key = keys[i];
    res[key] = opts[key];
  }
  return res;
};
 
exports.preprocessConfig = function(config) {
  // transfor ssrf.ipBlackList to ssrf.checkAddress
  // checkAddress has higher priority than ipBlackList
  const ssrf = config.ssrf;
  if (ssrf && ssrf.ipBlackList && !ssrf.checkAddress) {
    const containsList = ssrf.ipBlackList.map(getContains);
    ssrf.checkAddress = ip => {
      for (const contains of containsList) {
        if (contains(ip)) return false;
      }
      return true;
    };
  }
 
  // Make sure that `whiteList` or `protocolWhiteList` is case insensitive
  config.domainWhiteList = config.domainWhiteList || [];
  config.domainWhiteList = config.domainWhiteList.map(domain => domain.toLowerCase());
 
  config.protocolWhiteList = config.protocolWhiteList || [];
  config.protocolWhiteList = config.protocolWhiteList.map(protocol => protocol.toLowerCase());
 
  // Directly converted to Set collection by a private property (not documented),
  // And we NO LONGER need to do conversion in `foreach` again and again in `surl.js`.
  config._protocolWhiteListSet = new Set(config.protocolWhiteList);
  config._protocolWhiteListSet.add('http');
  config._protocolWhiteListSet.add('https');
  config._protocolWhiteListSet.add('file');
  config._protocolWhiteListSet.add('data');
};
 
function getContains(ip) {
  if (IP.isV4Format(ip) || IP.isV6Format(ip)) {
    return _ip => ip === _ip;
  }
  return IP.cidrSubnet(ip).contains;
}