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
'use strict';
 
const is = require('is-type-of');
const url = require('url');
const { JSONP_CONFIG } = require('../../lib/private_key');
 
module.exports = {
  /**
   * return a middleware to enable jsonp response.
   * will do some security check inside.
   * @param  {Object} options jsonp options. can override `config.jsonp`.
   * @return {Function} jsonp middleware
   * @public
   */
  jsonp(options) {
    const defaultOptions = this.config.jsonp;
    options = Object.assign({}, defaultOptions, options);
    if (!Array.isArray(options.callback)) options.callback = [ options.callback ];
 
    const csrfEnable = this.plugins.security && this.plugins.security.enable // security enable
      && this.config.security.csrf && this.config.security.csrf.enable !== false // csrf enable
      && options.csrf; // jsonp csrf enabled
 
    const validateReferrer = options.whiteList && createValidateReferer(options.whiteList);
 
    if (!csrfEnable && !validateReferrer) {
      this.coreLogger.warn('[egg-jsonp] SECURITY WARNING!! csrf check and referrer check are both closed!');
    }
    /**
     * jsonp request security check, pass if
     *
     * 1. hit referrer white list
     * 2. or pass csrf check
     * 3. both check are disabled
     *
     * @param  {Context} ctx request context
     */
    function securityAssert(ctx) {
      // all disabled. don't need check
      if (!csrfEnable && !validateReferrer) return;
 
      // pass referrer check
      const referrer = ctx.get('referrer');
      if (validateReferrer && validateReferrer(referrer)) return;
      if (csrfEnable && validateCsrf(ctx)) return;
 
      const err = new Error('jsonp request security validate failed');
      err.referrer = referrer;
      err.status = 403;
      throw err;
    }
 
    return async function jsonp(ctx, next) {
      const jsonpFunction = getJsonpFunction(ctx.query, options.callback);
 
      ctx[JSONP_CONFIG] = {
        jsonpFunction,
        options,
      };
 
      // before handle request, must do some security checks
      securityAssert(ctx);
 
      await next();
 
      // generate jsonp body
      ctx.createJsonpBody(ctx.body);
    };
  },
};
 
function createValidateReferer(whiteList) {
  if (!Array.isArray(whiteList)) whiteList = [ whiteList ];
 
  return function(referrer) {
    let parsed = null;
    for (const item of whiteList) {
      if (is.regExp(item) && item.test(referrer)) {
        // regexp(/^https?:\/\/github.com\//): test the referrer with item
        return true;
      }
 
      parsed = parsed || url.parse(referrer);
      const hostname = parsed.hostname || '';
 
      if (item[0] === '.' &&
        (hostname.endsWith(item) || hostname === item.slice(1))) {
        // string start with `.`(.github.com): referrer's hostname must ends with item
        return true;
      } else if (hostname === item) {
        // string not start with `.`(github.com): referrer's hostname must strict equal to item
        return true;
      }
    }
 
    return false;
  };
}
 
function validateCsrf(ctx) {
  try {
    ctx.assertCsrf();
    return true;
  } catch (_) {
    return false;
  }
}
 
function getJsonpFunction(query, callbacks) {
  for (const callback of callbacks) {
    if (query[callback]) return query[callback];
  }
}