t880216t-server 1.5.20

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.
Files changed (104) hide show
  1. package/LICENSE +124 -0
  2. package/README.md +191 -0
  3. package/bin/nohost.js +178 -0
  4. package/bin/plugin.js +70 -0
  5. package/bin/util.js +93 -0
  6. package/index.js +89 -0
  7. package/lib/config.js +42 -0
  8. package/lib/index.js +205 -0
  9. package/lib/main/cgi/getSettings.js +11 -0
  10. package/lib/main/cgi/getVersion.js +12 -0
  11. package/lib/main/cgi/login.js +14 -0
  12. package/lib/main/cgi/restart.js +5 -0
  13. package/lib/main/cgi/setAdmin.js +6 -0
  14. package/lib/main/cgi/setDomain.js +13 -0
  15. package/lib/main/cgi/status.js +122 -0
  16. package/lib/main/index.js +94 -0
  17. package/lib/main/router.js +79 -0
  18. package/lib/main/storage.js +132 -0
  19. package/lib/main/util.js +138 -0
  20. package/lib/main/whistleMgr.js +56 -0
  21. package/lib/main/worker.js +52 -0
  22. package/lib/main/workerNum.js +11 -0
  23. package/lib/plugins/account/whistle.share/menu.html +171 -0
  24. package/lib/plugins/account/whistle.share/package.json +20 -0
  25. package/lib/plugins/account/whistle.storage/index.js +43 -0
  26. package/lib/plugins/account/whistle.storage/package.json +8 -0
  27. package/lib/plugins/account/whistle.storage/rules.txt +1 -0
  28. package/lib/plugins/whistle.nohost/_rules.txt +38 -0
  29. package/lib/plugins/whistle.nohost/index.js +3 -0
  30. package/lib/plugins/whistle.nohost/initial.js +9 -0
  31. package/lib/plugins/whistle.nohost/lib/accountMgr.js +750 -0
  32. package/lib/plugins/whistle.nohost/lib/envMgr.js +114 -0
  33. package/lib/plugins/whistle.nohost/lib/rulesServer.js +85 -0
  34. package/lib/plugins/whistle.nohost/lib/tunnelRulesServer.js +71 -0
  35. package/lib/plugins/whistle.nohost/lib/uiServer/cgi/account/changePassword.js +8 -0
  36. package/lib/plugins/whistle.nohost/lib/uiServer/cgi/admin/activeAccount.js +9 -0
  37. package/lib/plugins/whistle.nohost/lib/uiServer/cgi/admin/addAccount.js +8 -0
  38. package/lib/plugins/whistle.nohost/lib/uiServer/cgi/admin/allAccounts.js +9 -0
  39. package/lib/plugins/whistle.nohost/lib/uiServer/cgi/admin/changeNotice.js +8 -0
  40. package/lib/plugins/whistle.nohost/lib/uiServer/cgi/admin/changePassword.js +8 -0
  41. package/lib/plugins/whistle.nohost/lib/uiServer/cgi/admin/enableGuest.js +5 -0
  42. package/lib/plugins/whistle.nohost/lib/uiServer/cgi/admin/getAuthKey.js +3 -0
  43. package/lib/plugins/whistle.nohost/lib/uiServer/cgi/admin/getSettings.js +22 -0
  44. package/lib/plugins/whistle.nohost/lib/uiServer/cgi/admin/login.js +14 -0
  45. package/lib/plugins/whistle.nohost/lib/uiServer/cgi/admin/move.js +9 -0
  46. package/lib/plugins/whistle.nohost/lib/uiServer/cgi/admin/removeAccount.js +9 -0
  47. package/lib/plugins/whistle.nohost/lib/uiServer/cgi/admin/setAccountRules.js +5 -0
  48. package/lib/plugins/whistle.nohost/lib/uiServer/cgi/admin/setAuthKey.js +5 -0
  49. package/lib/plugins/whistle.nohost/lib/uiServer/cgi/admin/setDefaultRules.js +5 -0
  50. package/lib/plugins/whistle.nohost/lib/uiServer/cgi/admin/setEntryPatterns.js +6 -0
  51. package/lib/plugins/whistle.nohost/lib/uiServer/cgi/admin/setJsonData.js +5 -0
  52. package/lib/plugins/whistle.nohost/lib/uiServer/cgi/admin/setRulesTpl.js +5 -0
  53. package/lib/plugins/whistle.nohost/lib/uiServer/cgi/admin/setTestRules.js +5 -0
  54. package/lib/plugins/whistle.nohost/lib/uiServer/cgi/admin/specPattern.js +4 -0
  55. package/lib/plugins/whistle.nohost/lib/uiServer/cgi/allowlist.js +4 -0
  56. package/lib/plugins/whistle.nohost/lib/uiServer/cgi/entryRules.js +4 -0
  57. package/lib/plugins/whistle.nohost/lib/uiServer/cgi/follow.js +17 -0
  58. package/lib/plugins/whistle.nohost/lib/uiServer/cgi/getEnv.js +12 -0
  59. package/lib/plugins/whistle.nohost/lib/uiServer/cgi/list.js +43 -0
  60. package/lib/plugins/whistle.nohost/lib/uiServer/cgi/patterns.js +14 -0
  61. package/lib/plugins/whistle.nohost/lib/uiServer/cgi/pluginRules.js +61 -0
  62. package/lib/plugins/whistle.nohost/lib/uiServer/cgi/proxy.js +42 -0
  63. package/lib/plugins/whistle.nohost/lib/uiServer/cgi/proxyNetwork.js +12 -0
  64. package/lib/plugins/whistle.nohost/lib/uiServer/cgi/redirect.js +10 -0
  65. package/lib/plugins/whistle.nohost/lib/uiServer/cgi/selectEnv.js +30 -0
  66. package/lib/plugins/whistle.nohost/lib/uiServer/cgi/unfollow.js +4 -0
  67. package/lib/plugins/whistle.nohost/lib/uiServer/index.js +60 -0
  68. package/lib/plugins/whistle.nohost/lib/uiServer/network.js +38 -0
  69. package/lib/plugins/whistle.nohost/lib/uiServer/openAPI.js +94 -0
  70. package/lib/plugins/whistle.nohost/lib/uiServer/router.js +85 -0
  71. package/lib/plugins/whistle.nohost/lib/util.js +230 -0
  72. package/lib/plugins/whistle.nohost/lib/whistle.js +149 -0
  73. package/lib/plugins/whistle.nohost/lib/whistleMgr.js +94 -0
  74. package/lib/plugins/whistle.nohost/package.json +7 -0
  75. package/lib/plugins/whistle.nohost/rules.txt +3 -0
  76. package/lib/service/cgi/export.js +76 -0
  77. package/lib/service/cgi/import.js +33 -0
  78. package/lib/service/cgi/util.js +35 -0
  79. package/lib/service/index.js +37 -0
  80. package/lib/service/router.js +7 -0
  81. package/lib/service/server.js +25 -0
  82. package/lib/util/address.js +55 -0
  83. package/lib/util/getPort.js +19 -0
  84. package/lib/util/login.js +55 -0
  85. package/lib/util/parseDomain.js +17 -0
  86. package/lib/whistle.js +86 -0
  87. package/package.json +135 -0
  88. package/pnpm-workspace.yaml +6 -0
  89. package/public/admin.75d42731d4aa7f7a558d3abf2a375fcb.js +2 -0
  90. package/public/admin.75d42731d4aa7f7a558d3abf2a375fcb.js.LICENSE.txt +76 -0
  91. package/public/admin.html +11 -0
  92. package/public/button.js +2 -0
  93. package/public/button.js.LICENSE.txt +31 -0
  94. package/public/capture.7bab900f27c9bb1b0e33523e994554ae.js +2 -0
  95. package/public/capture.7bab900f27c9bb1b0e33523e994554ae.js.LICENSE.txt +83 -0
  96. package/public/capture.html +11 -0
  97. package/public/eed368d0656f03932671530c3132ed61.svg +1 -0
  98. package/public/favicon.ico +0 -0
  99. package/public/network.e4814ec8c966a8a789296679619ec573.js +2 -0
  100. package/public/network.e4814ec8c966a8a789296679619ec573.js.LICENSE.txt +76 -0
  101. package/public/network.html +11 -0
  102. package/public/select.cb638be6656d02a558f2690acd59901a.js +2 -0
  103. package/public/select.cb638be6656d02a558f2690acd59901a.js.LICENSE.txt +24 -0
  104. package/public/select.html +26 -0
@@ -0,0 +1,10 @@
1
+ const { getClientId } = require('../../util');
2
+
3
+ module.exports = (ctx) => {
4
+ const { url, name, env } = ctx.request.query;
5
+ const account = ctx.accountMgr.getAccount(name);
6
+ if (account) {
7
+ ctx.envMgr.setEnv(getClientId(ctx), name, env);
8
+ }
9
+ ctx.redirect(url || './');
10
+ };
@@ -0,0 +1,30 @@
1
+ const { COOKIE_NAME, ENV_MAX_AGE, decodeURIComponentSafe, getClientId } = require('../../util');
2
+
3
+ module.exports = (ctx) => {
4
+ const { name, envId, redirect } = ctx.request.query;
5
+ let envName;
6
+ // 如果 envId 看起来像短ID(不包含编码的环境名特征),则通过 envId 查找 envName
7
+ if (envId && !envId.includes('%')) {
8
+ envName = ctx.accountMgr.getEnvNameById(name, envId);
9
+ // 如果没找到,回退到直接解码(兼容旧逻辑)
10
+ if (!envName) {
11
+ envName = decodeURIComponentSafe(envId);
12
+ }
13
+ } else {
14
+ envName = decodeURIComponentSafe(envId);
15
+ }
16
+ const env = ctx.envMgr.setEnv(getClientId(ctx), name, envName);
17
+ let value = '';
18
+ if (env) {
19
+ value = encodeURIComponent(`${name}/${env.envName}`);
20
+ }
21
+ ctx.cookies.set(COOKIE_NAME, value, {
22
+ path: '/',
23
+ expires: new Date(Date.now() + (ENV_MAX_AGE * 1000)),
24
+ });
25
+ if (redirect && typeof redirect === 'string') {
26
+ ctx.redirect(redirect);
27
+ } else {
28
+ ctx.body = { ec: 0 };
29
+ }
30
+ };
@@ -0,0 +1,4 @@
1
+ module.exports = (ctx) => {
2
+ ctx.envMgr.unfollow(ctx);
3
+ ctx.body = { ec: 0 };
4
+ };
@@ -0,0 +1,60 @@
1
+ const Koa = require('koa');
2
+ const onerror = require('koa-onerror');
3
+ const serve = require('koa-static');
4
+ const path = require('path');
5
+ const router = require('koa-router')();
6
+ const setupRouter = require('./router');
7
+ const accountMgr = require('../accountMgr');
8
+ const envMgr = require('../envMgr');
9
+ const whistleMgr = require('../whistleMgr');
10
+
11
+ const MAX_AGE = 1000 * 60 * 5;
12
+ const error = function (em, ec) {
13
+ em = em || '请求失败,请刷新页面重试';
14
+ ec = ec || 2;
15
+ this.body = { ec, em };
16
+ };
17
+
18
+ module.exports = (server, options) => {
19
+ const { username, password } = options.data;
20
+ const { runtimeId } = options.config;
21
+ const app = new Koa();
22
+ app.proxy = true;
23
+ app.silent = true;
24
+ if (process.env.PFORK_MODE === 'bind') {
25
+ onerror(app);
26
+ }
27
+ app.use(async (ctx, next) => {
28
+ const baseUrl = `http://${accountMgr.getBaseUrl()}/`;
29
+ ctx.baseUrl = baseUrl;
30
+ ctx.success = function () {
31
+ this.body = { ec: 0, baseUrl };
32
+ };
33
+ ctx.error = error;
34
+ ctx.accountMgr = accountMgr;
35
+ ctx.envMgr = envMgr;
36
+ ctx.config = options.config;
37
+ ctx.updateRules = options.updateRules;
38
+ ctx.whistleMgr = whistleMgr;
39
+ ctx.admin = { username, password };
40
+ ctx.runtimeId = runtimeId;
41
+ ctx.set('x-nohost-base-url', baseUrl);
42
+ const { path: pathname } = ctx;
43
+ if (pathname === '/' || pathname === '/index.html') {
44
+ ctx.req.url = '/select.html';
45
+ } else if (pathname === '/js/nohost.js') {
46
+ ctx.req.url = '/button.js';
47
+ }
48
+ const origin = ctx.get('origin');
49
+ if (origin) {
50
+ ctx.set('Access-Control-Allow-Origin', origin);
51
+ ctx.set('Access-Control-Allow-Credentials', true);
52
+ }
53
+ await next();
54
+ });
55
+ setupRouter(router);
56
+ app.use(router.routes());
57
+ app.use(router.allowedMethods());
58
+ app.use(serve(path.join(__dirname, '../../../../../public/'), { maxage: MAX_AGE }));
59
+ server.on('request', app.callback());
60
+ };
@@ -0,0 +1,38 @@
1
+
2
+ const startWhistle = require('whistle');
3
+ const path = require('path');
4
+ const { getNohostPluginsPath, getPort, PLUGINS_DIR } = require('../util');
5
+
6
+ let result;
7
+ const projectPluginsPath = [
8
+ path.join(__dirname, '../../../account'),
9
+ ];
10
+ const customPluginsPath = [
11
+ path.join(getNohostPluginsPath(), 'worker_plugins'),
12
+ ];
13
+ const pluginsPath = [
14
+ path.join(PLUGINS_DIR, 'whistle.nohost/lib/node_modules'),
15
+ path.join(PLUGINS_DIR, 'whistle.nohost/node_modules'),
16
+ ];
17
+
18
+ module.exports = () => {
19
+ if (!result) {
20
+ result = new Promise((resolve) => {
21
+ getPort((port) => {
22
+ const proxy = startWhistle({
23
+ globalData: 'Network',
24
+ cmdName: 'n2',
25
+ port,
26
+ host: '127.0.0.1',
27
+ encrypted: true,
28
+ storage: 'nohost_network_mode',
29
+ mode: 'plugins|disableUpdateTips|hideLeftMenu',
30
+ projectPluginsPath,
31
+ customPluginsPath,
32
+ pluginsPath,
33
+ }, () => resolve(port, proxy));
34
+ });
35
+ });
36
+ }
37
+ return result;
38
+ };
@@ -0,0 +1,94 @@
1
+ const proxy = require('./cgi/proxy');
2
+ const accountMgr = require('../accountMgr');
3
+ const { AUTH_KEY } = require('../util');
4
+
5
+ const CGI_MAP = {
6
+ 'add-env': 'rules/add',
7
+ 'add-top-env': 'rules/project',
8
+ 'add-top-rules': 'rules/project',
9
+ 'add-rules': 'rules/add',
10
+ 'modify-env': 'rules/add',
11
+ 'modify-rules': 'rules/add',
12
+ 'remove-env': 'rules/remove',
13
+ 'remove-rules': 'rules/remove',
14
+ 'rename-env': 'rules/rename',
15
+ 'rename-rules': 'rules/rename',
16
+ list: 'rules/list',
17
+ };
18
+
19
+ const getRules = (options) => {
20
+ const { account, name, env, envName, envId } = options;
21
+ let rules = '';
22
+ let headers;
23
+ // 如果传入了 envId,优先使用 envId 查询
24
+ if (envId && (account || name)) {
25
+ const result = accountMgr.getRulesByEnvId(account || name, envId);
26
+ rules = result.rules || '';
27
+ headers = result.headers;
28
+ } else {
29
+ const result = accountMgr.getRules(account || name, env || envName);
30
+ rules = result.rules || '';
31
+ headers = result.headers;
32
+ }
33
+ if (headers) {
34
+ headers = JSON.stringify(headers);
35
+ if (headers.length > 2) {
36
+ rules += `\n* reqHeaders://(${headers})`;
37
+ }
38
+ }
39
+ return rules;
40
+ };
41
+
42
+ module.exports = async (ctx, next) => {
43
+ let { cgiName } = ctx.params;
44
+ const authKey = ctx.accountMgr.getAuthKey();
45
+ if (cgiName === 'rules') {
46
+ const { query } = ctx.request;
47
+ if (!authKey || authKey !== query.authKey) {
48
+ ctx.status = 403;
49
+ return;
50
+ }
51
+ ctx.body = getRules(query);
52
+ return;
53
+ }
54
+
55
+ // Handle rename-env specially to preserve env id
56
+ if (cgiName === 'rename-env') {
57
+ const name = ctx.get('x-nohost-account-name');
58
+ const account = ctx.accountMgr.getAccount(name);
59
+ if (!account) {
60
+ return;
61
+ }
62
+ const curAuthKey = authKey && ctx.get('x-nohost-auth-key');
63
+ if (!curAuthKey || (authKey !== curAuthKey && curAuthKey !== ctx.accountMgr.defaultAuthKey)) {
64
+ ctx.status = 403;
65
+ return;
66
+ }
67
+ const { query } = ctx.request;
68
+ const oldName = query.name;
69
+ const newName = query.newValue || query.newName;
70
+ if (oldName && newName) {
71
+ ctx.accountMgr.renameEnv(name, oldName, newName);
72
+ }
73
+ }
74
+
75
+ cgiName = CGI_MAP[cgiName];
76
+ const name = ctx.get('x-nohost-account-name');
77
+ const account = cgiName && ctx.accountMgr.getAccount(name);
78
+ if (!account) {
79
+ return;
80
+ }
81
+ const curAuthKey = authKey && ctx.get('x-nohost-auth-key');
82
+ if (!curAuthKey || (authKey !== curAuthKey && curAuthKey !== ctx.accountMgr.defaultAuthKey)) {
83
+ ctx.status = 403;
84
+ return;
85
+ }
86
+ const { req } = ctx;
87
+ req.headers['x-whistle-auth-key'] = AUTH_KEY;
88
+ let { url } = req;
89
+ const index = url.indexOf('?');
90
+ url = index === -1 ? '' : url.substring(index);
91
+ req.url = `/account/${name}/cgi-bin/${cgiName}${url}`;
92
+ ctx.account = account;
93
+ await proxy(ctx, next);
94
+ };
@@ -0,0 +1,85 @@
1
+ const bodyParser = require('koa-bodyparser');
2
+ const list = require('./cgi/list');
3
+ const proxyNetwork = require('./cgi/proxyNetwork');
4
+ const selectEnv = require('./cgi/selectEnv');
5
+ const checkAdminLogin = require('./cgi/admin/login');
6
+ const getAllAccounts = require('./cgi/admin/allAccounts');
7
+ const addAccount = require('./cgi/admin/addAccount');
8
+ const moveAccount = require('./cgi/admin/move');
9
+ const activeAccount = require('./cgi/admin/activeAccount');
10
+ const removeAccount = require('./cgi/admin/removeAccount');
11
+ const getSettings = require('./cgi/admin/getSettings');
12
+ const setJsonData = require('./cgi/admin/setJsonData');
13
+ const setRulesTpl = require('./cgi/admin/setRulesTpl');
14
+ const setEntryPatterns = require('./cgi/admin/setEntryPatterns');
15
+ const setTestRules = require('./cgi/admin/setTestRules');
16
+ const setDefaultRules = require('./cgi/admin/setDefaultRules');
17
+ const setAccountRules = require('./cgi/admin/setAccountRules');
18
+ const getAuthKey = require('./cgi/admin/getAuthKey');
19
+ const setAuthKey = require('./cgi/admin/setAuthKey');
20
+ const enableGuest = require('./cgi/admin/enableGuest');
21
+ const proxy = require('./cgi/proxy');
22
+ const getEntryRules = require('./cgi/entryRules');
23
+ const getPluginRules = require('./cgi/pluginRules');
24
+ const changePassword = require('./cgi/account/changePassword');
25
+ const patterns = require('./cgi/patterns');
26
+ const allowlist = require('./cgi/allowlist');
27
+ const openAPI = require('./openAPI');
28
+ const follow = require('./cgi/follow');
29
+ const unfollow = require('./cgi/unfollow');
30
+ const redirect = require('./cgi/redirect');
31
+ const getEnv = require('./cgi/getEnv');
32
+ const changeAdminPassword = require('./cgi/admin/changePassword');
33
+ const changeNotice = require('./cgi/admin/changeNotice');
34
+ const setSpecPattern = require('./cgi/admin/specPattern');
35
+
36
+ const defaultBodyParser = bodyParser({ formLimit: '1mb' });
37
+ const forwardCapture = async (ctx, next) => {
38
+ ctx.url = '/capture.html';
39
+ await next();
40
+ };
41
+
42
+ module.exports = (router) => {
43
+ router.get('/admin.html', checkAdminLogin);
44
+ router.all('/cgi-bin/admin/(.*)', checkAdminLogin);
45
+ router.all('/cgi-bin/(.*)', defaultBodyParser);
46
+ router.get('/open-api/select', defaultBodyParser, selectEnv);
47
+ router.get('/open-api/cur-env', defaultBodyParser, getEnv);
48
+ router.get('/open-api/get-env', defaultBodyParser, getEnv);
49
+ router.all('/open-api/:cgiName', openAPI);
50
+ router.get('/cgi-bin/list', list);
51
+ router.get('/cgi-bin/cur-env', getEnv);
52
+ router.get('/cgi-bin/get-env', getEnv);
53
+ router.get('/cgi-bin/rules', getEntryRules);
54
+ router.get('/cgi-bin/entry-rules', getEntryRules);
55
+ router.get('/cgi-bin/remote-rules', getPluginRules);
56
+ router.get('/cgi-bin/plugin-rules', getPluginRules);
57
+ router.all('/network/(.*)', proxyNetwork);
58
+ router.get('/data.html', forwardCapture);
59
+ router.get('/cgi-bin/select', selectEnv);
60
+ router.get('/cgi-bin/admin/all-accounts', getAllAccounts);
61
+ router.post('/cgi-bin/admin/add-account', addAccount);
62
+ router.post('/cgi-bin/admin/move', moveAccount);
63
+ router.post('/cgi-bin/admin/active-account', activeAccount);
64
+ router.post('/cgi-bin/admin/remove-account', removeAccount);
65
+ router.post('/cgi-bin/admin/change-password', changeAdminPassword);
66
+ router.post('/cgi-bin/admin/change-notice', changeNotice);
67
+ router.post('/cgi-bin/admin/enable-guest', enableGuest);
68
+ router.post('/cgi-bin/admin/set-entry-patterns', setEntryPatterns);
69
+ router.get('/cgi-bin/admin/get-settings', getSettings);
70
+ router.post('/cgi-bin/admin/set-json-data', setJsonData);
71
+ router.post('/cgi-bin/admin/set-rules-tpl', setRulesTpl);
72
+ router.post('/cgi-bin/admin/set-test-rules', setTestRules);
73
+ router.post('/cgi-bin/admin/set-default-rules', setDefaultRules);
74
+ router.post('/cgi-bin/admin/set-account-rules', setAccountRules);
75
+ router.get('/cgi-bin/admin/get-auth-key', getAuthKey);
76
+ router.post('/cgi-bin/admin/set-auth-key', setAuthKey);
77
+ router.post('/cgi-bin/admin/set-spec-pattern', setSpecPattern);
78
+ router.get('/cgi-bin/patterns', patterns);
79
+ router.get('/cgi-bin/allowlist', allowlist);
80
+ router.all('/account/:name/(.*)', proxy);
81
+ router.post('/cgi-bin/account/change-password', changePassword);
82
+ router.get('/follow', follow);
83
+ router.get('/unfollow', unfollow);
84
+ router.get('/redirect', redirect);
85
+ };
@@ -0,0 +1,230 @@
1
+ const crypto = require('crypto');
2
+ const net = require('net');
3
+ const parseurl = require('parseurl');
4
+ const http = require('http');
5
+ const HOME_DIR = require('os').homedir();
6
+ const path = require('path');
7
+ const { getWhistlePath } = require('whistle');
8
+
9
+ const ENV_MAX_AGE = 60 * 60 * 24 * 3;
10
+ const MAX_CLIENT_ID_LEN = 100;
11
+ const CONF_KEY_RE = /^([\w-]{1,64}:?|[\w.-]{1,64}:)$/;
12
+
13
+ exports.AUTH_KEY = `${Date.now()}/${Math.random()}`;
14
+ exports.loadModule = require;
15
+ exports.ENV_MAX_AGE = ENV_MAX_AGE;
16
+ exports.WHISTLE_ENV_HEADER = 'x-whistle-nohost-env';
17
+ exports.CONFIG_DATA_TYPE = 'PROXY_CONFIG';
18
+ exports.COOKIE_NAME = 'whistle_nohost_env';
19
+ exports.WHISTLE_RULE_VALUE = 'x-whistle-rule-value';
20
+ exports.BASE_URL = 'http://local.whistlejs.com/plugin.nohost/';
21
+
22
+ const PLUGINS_DIR = path.resolve(HOME_DIR, 'whistle-plugins');
23
+ exports.PLUGINS_DIR = PLUGINS_DIR;
24
+
25
+ const LOCALHOST = '127.0.0.1';
26
+ const STRIDE = 5;
27
+ let curPort = Math.floor(20001 + (10000 * Math.random()));
28
+ curPort = (curPort + 1) - (curPort % STRIDE);
29
+
30
+ const getPort = (callback) => {
31
+ const server = http.createServer();
32
+ server.on('error', () => {
33
+ if (++curPort % 5 === 0) {
34
+ ++curPort;
35
+ }
36
+ getPort(callback);
37
+ });
38
+ server.listen(curPort, LOCALHOST, () => {
39
+ server.removeAllListeners();
40
+ server.close(() => callback(curPort));
41
+ });
42
+ };
43
+
44
+ exports.getPort = getPort;
45
+
46
+ /* eslint-disable no-empty */
47
+ const shasum = (str) => {
48
+ if (typeof str !== 'string') {
49
+ str = '';
50
+ }
51
+ const result = crypto.createHash('sha1');
52
+ result.update(str);
53
+ return result.digest('hex');
54
+ };
55
+ exports.shasum = shasum;
56
+
57
+ const decodeURIComponentSafe = (str) => {
58
+ if (typeof str !== 'string') {
59
+ return '';
60
+ }
61
+ try {
62
+ return decodeURIComponent(str);
63
+ } catch (e) {}
64
+ return str;
65
+ };
66
+
67
+ exports.decodeURIComponentSafe = decodeURIComponentSafe;
68
+
69
+ const parseJSON = (str) => {
70
+ try {
71
+ const result = JSON.parse(str);
72
+ if (typeof result === 'object') {
73
+ return result;
74
+ }
75
+ } catch (e) {}
76
+ };
77
+
78
+ exports.parseJSON = parseJSON;
79
+
80
+ const transformReq = (req, port, host) => {
81
+ const options = parseurl(req);
82
+ options.host = host || '127.0.0.1';
83
+ options.method = req.method;
84
+ options.headers = req.headers;
85
+ delete options.headers.referer;
86
+ delete options.protocol;
87
+ delete options.hostname;
88
+ if (port > 0) {
89
+ options.port = port;
90
+ }
91
+ return new Promise((resolve, reject) => {
92
+ const client = http.request(options, resolve);
93
+ client.on('error', reject);
94
+ req.pipe(client);
95
+ });
96
+ };
97
+
98
+ exports.transformReq = transformReq;
99
+ exports.transformWhistle = async (ctx, port) => {
100
+ const { req } = ctx;
101
+ const res = await transformReq(req, port);
102
+ ctx.status = res.statusCode;
103
+ ctx.set(res.headers);
104
+ ctx.body = res;
105
+ };
106
+
107
+ exports.parseConfig = (ctn) => {
108
+ if (typeof ctn !== 'string') {
109
+ return;
110
+ }
111
+ ctn = ctn.trim().split(/\r\n|\r|\n/g);
112
+ let conf;
113
+ ctn.forEach((line) => {
114
+ line = line.replace(/#.*$/, '').trim().split(/\s+/);
115
+ if (line.length) {
116
+ let key = line[0].toLowerCase();
117
+ if (!key || !CONF_KEY_RE.test(key)) {
118
+ return;
119
+ }
120
+ conf = conf || {};
121
+ key = key.replace(':', '');
122
+ if (key !== 'host') {
123
+ key = `x-nohost-${key}`;
124
+ }
125
+ if (conf[key] == null && (!conf.headers || conf.headers[key] == null)) {
126
+ if (key === 'host') {
127
+ conf.host = line[1];
128
+ } else {
129
+ conf.headers = conf.headers || {};
130
+ conf.headers[key] = line[1] || '';
131
+ }
132
+ }
133
+ }
134
+ });
135
+ return conf;
136
+ };
137
+
138
+ const KEY_RE = /(?:@([^\s@]+)|\{([^\s@]+)\})/ig;
139
+ const resolveConf = (ctn, values) => {
140
+ if (!ctn || !values) {
141
+ return ctn;
142
+ }
143
+ return ctn.replace(KEY_RE, (all, $1, $2) => {
144
+ return values[$1 || $2] || '';
145
+ });
146
+ };
147
+
148
+ exports.resolveConfList = (list, publicList) => {
149
+ if (!list.length || !publicList || !publicList.length) {
150
+ return list;
151
+ }
152
+ const map = {};
153
+ publicList.forEach((conf) => {
154
+ map[conf.name] = conf.rules || '';
155
+ });
156
+ list.forEach((conf) => {
157
+ conf.rules = resolveConf(conf.rules, map);
158
+ });
159
+ return list;
160
+ };
161
+
162
+ let REQ_FROM_HEADER;
163
+ let RULE_VALUE_HEADER;
164
+
165
+ exports.initPlugin = (options) => {
166
+ RULE_VALUE_HEADER = options.RULE_VALUE_HEADER;
167
+ REQ_FROM_HEADER = options.config.REQ_FROM_HEADER;
168
+ exports.config = options.config;
169
+ exports.pluginConfig = options.data;
170
+ };
171
+
172
+ exports.getRuleValue = (ctx) => {
173
+ const ruleValue = ctx.get(RULE_VALUE_HEADER);
174
+ return ruleValue ? decodeURIComponent(ruleValue) : '';
175
+ };
176
+
177
+ exports.isFromComposer = (ctx) => {
178
+ return ctx.get(REQ_FROM_HEADER) === 'W2COMPOSER';
179
+ };
180
+
181
+ exports.getDomain = function (hostname) {
182
+ if (net.isIP(hostname)) {
183
+ return hostname;
184
+ }
185
+ let list = hostname.split('.');
186
+ const len = list.length;
187
+ if (len < 3) {
188
+ return hostname;
189
+ }
190
+ let wildcard = len > 3;
191
+ if (wildcard) {
192
+ list = list.slice(-3);
193
+ }
194
+
195
+ if (list[1].length > 3 || list[1] === 'url' || list[2] === 'com') {
196
+ wildcard = true;
197
+ list = list.slice(-2);
198
+ }
199
+ if (wildcard) {
200
+ list.unshift('');
201
+ }
202
+ return list.join('.');
203
+ };
204
+
205
+ exports.isClientId = (str) => {
206
+ if (!str || typeof str !== 'string') {
207
+ return false;
208
+ }
209
+ return str.length <= MAX_CLIENT_ID_LEN;
210
+ };
211
+
212
+ exports.getClientId = (ctx) => {
213
+ const clientId = ctx.get('x-whistle-nohost-client-id')
214
+ || ctx.get('x-whistle-client-id');
215
+ if (clientId && typeof clientId === 'string') {
216
+ return (clientId.trim() || ctx.ip).substring(0, MAX_CLIENT_ID_LEN);
217
+ }
218
+ return ctx.ip;
219
+ };
220
+
221
+ exports.changeFilter = (req, followerIp) => {
222
+ if (!req.url.indexOf('/cgi-bin/get-data?') && req.url.indexOf('ip=self') !== -1) {
223
+ followerIp = followerIp ? `,${followerIp}` : '';
224
+ req.url = req.url.replace('ip=self', `ip=clientId${followerIp}`);
225
+ }
226
+ };
227
+
228
+ exports.getNohostPluginsPath = () => {
229
+ return path.join(getWhistlePath(), 'nohost_plugins');
230
+ };