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.
- package/LICENSE +124 -0
- package/README.md +191 -0
- package/bin/nohost.js +178 -0
- package/bin/plugin.js +70 -0
- package/bin/util.js +93 -0
- package/index.js +89 -0
- package/lib/config.js +42 -0
- package/lib/index.js +205 -0
- package/lib/main/cgi/getSettings.js +11 -0
- package/lib/main/cgi/getVersion.js +12 -0
- package/lib/main/cgi/login.js +14 -0
- package/lib/main/cgi/restart.js +5 -0
- package/lib/main/cgi/setAdmin.js +6 -0
- package/lib/main/cgi/setDomain.js +13 -0
- package/lib/main/cgi/status.js +122 -0
- package/lib/main/index.js +94 -0
- package/lib/main/router.js +79 -0
- package/lib/main/storage.js +132 -0
- package/lib/main/util.js +138 -0
- package/lib/main/whistleMgr.js +56 -0
- package/lib/main/worker.js +52 -0
- package/lib/main/workerNum.js +11 -0
- package/lib/plugins/account/whistle.share/menu.html +171 -0
- package/lib/plugins/account/whistle.share/package.json +20 -0
- package/lib/plugins/account/whistle.storage/index.js +43 -0
- package/lib/plugins/account/whistle.storage/package.json +8 -0
- package/lib/plugins/account/whistle.storage/rules.txt +1 -0
- package/lib/plugins/whistle.nohost/_rules.txt +38 -0
- package/lib/plugins/whistle.nohost/index.js +3 -0
- package/lib/plugins/whistle.nohost/initial.js +9 -0
- package/lib/plugins/whistle.nohost/lib/accountMgr.js +750 -0
- package/lib/plugins/whistle.nohost/lib/envMgr.js +114 -0
- package/lib/plugins/whistle.nohost/lib/rulesServer.js +85 -0
- package/lib/plugins/whistle.nohost/lib/tunnelRulesServer.js +71 -0
- package/lib/plugins/whistle.nohost/lib/uiServer/cgi/account/changePassword.js +8 -0
- package/lib/plugins/whistle.nohost/lib/uiServer/cgi/admin/activeAccount.js +9 -0
- package/lib/plugins/whistle.nohost/lib/uiServer/cgi/admin/addAccount.js +8 -0
- package/lib/plugins/whistle.nohost/lib/uiServer/cgi/admin/allAccounts.js +9 -0
- package/lib/plugins/whistle.nohost/lib/uiServer/cgi/admin/changeNotice.js +8 -0
- package/lib/plugins/whistle.nohost/lib/uiServer/cgi/admin/changePassword.js +8 -0
- package/lib/plugins/whistle.nohost/lib/uiServer/cgi/admin/enableGuest.js +5 -0
- package/lib/plugins/whistle.nohost/lib/uiServer/cgi/admin/getAuthKey.js +3 -0
- package/lib/plugins/whistle.nohost/lib/uiServer/cgi/admin/getSettings.js +22 -0
- package/lib/plugins/whistle.nohost/lib/uiServer/cgi/admin/login.js +14 -0
- package/lib/plugins/whistle.nohost/lib/uiServer/cgi/admin/move.js +9 -0
- package/lib/plugins/whistle.nohost/lib/uiServer/cgi/admin/removeAccount.js +9 -0
- package/lib/plugins/whistle.nohost/lib/uiServer/cgi/admin/setAccountRules.js +5 -0
- package/lib/plugins/whistle.nohost/lib/uiServer/cgi/admin/setAuthKey.js +5 -0
- package/lib/plugins/whistle.nohost/lib/uiServer/cgi/admin/setDefaultRules.js +5 -0
- package/lib/plugins/whistle.nohost/lib/uiServer/cgi/admin/setEntryPatterns.js +6 -0
- package/lib/plugins/whistle.nohost/lib/uiServer/cgi/admin/setJsonData.js +5 -0
- package/lib/plugins/whistle.nohost/lib/uiServer/cgi/admin/setRulesTpl.js +5 -0
- package/lib/plugins/whistle.nohost/lib/uiServer/cgi/admin/setTestRules.js +5 -0
- package/lib/plugins/whistle.nohost/lib/uiServer/cgi/admin/specPattern.js +4 -0
- package/lib/plugins/whistle.nohost/lib/uiServer/cgi/allowlist.js +4 -0
- package/lib/plugins/whistle.nohost/lib/uiServer/cgi/entryRules.js +4 -0
- package/lib/plugins/whistle.nohost/lib/uiServer/cgi/follow.js +17 -0
- package/lib/plugins/whistle.nohost/lib/uiServer/cgi/getEnv.js +12 -0
- package/lib/plugins/whistle.nohost/lib/uiServer/cgi/list.js +43 -0
- package/lib/plugins/whistle.nohost/lib/uiServer/cgi/patterns.js +14 -0
- package/lib/plugins/whistle.nohost/lib/uiServer/cgi/pluginRules.js +61 -0
- package/lib/plugins/whistle.nohost/lib/uiServer/cgi/proxy.js +42 -0
- package/lib/plugins/whistle.nohost/lib/uiServer/cgi/proxyNetwork.js +12 -0
- package/lib/plugins/whistle.nohost/lib/uiServer/cgi/redirect.js +10 -0
- package/lib/plugins/whistle.nohost/lib/uiServer/cgi/selectEnv.js +30 -0
- package/lib/plugins/whistle.nohost/lib/uiServer/cgi/unfollow.js +4 -0
- package/lib/plugins/whistle.nohost/lib/uiServer/index.js +60 -0
- package/lib/plugins/whistle.nohost/lib/uiServer/network.js +38 -0
- package/lib/plugins/whistle.nohost/lib/uiServer/openAPI.js +94 -0
- package/lib/plugins/whistle.nohost/lib/uiServer/router.js +85 -0
- package/lib/plugins/whistle.nohost/lib/util.js +230 -0
- package/lib/plugins/whistle.nohost/lib/whistle.js +149 -0
- package/lib/plugins/whistle.nohost/lib/whistleMgr.js +94 -0
- package/lib/plugins/whistle.nohost/package.json +7 -0
- package/lib/plugins/whistle.nohost/rules.txt +3 -0
- package/lib/service/cgi/export.js +76 -0
- package/lib/service/cgi/import.js +33 -0
- package/lib/service/cgi/util.js +35 -0
- package/lib/service/index.js +37 -0
- package/lib/service/router.js +7 -0
- package/lib/service/server.js +25 -0
- package/lib/util/address.js +55 -0
- package/lib/util/getPort.js +19 -0
- package/lib/util/login.js +55 -0
- package/lib/util/parseDomain.js +17 -0
- package/lib/whistle.js +86 -0
- package/package.json +135 -0
- package/pnpm-workspace.yaml +6 -0
- package/public/admin.75d42731d4aa7f7a558d3abf2a375fcb.js +2 -0
- package/public/admin.75d42731d4aa7f7a558d3abf2a375fcb.js.LICENSE.txt +76 -0
- package/public/admin.html +11 -0
- package/public/button.js +2 -0
- package/public/button.js.LICENSE.txt +31 -0
- package/public/capture.7bab900f27c9bb1b0e33523e994554ae.js +2 -0
- package/public/capture.7bab900f27c9bb1b0e33523e994554ae.js.LICENSE.txt +83 -0
- package/public/capture.html +11 -0
- package/public/eed368d0656f03932671530c3132ed61.svg +1 -0
- package/public/favicon.ico +0 -0
- package/public/network.e4814ec8c966a8a789296679619ec573.js +2 -0
- package/public/network.e4814ec8c966a8a789296679619ec573.js.LICENSE.txt +76 -0
- package/public/network.html +11 -0
- package/public/select.cb638be6656d02a558f2690acd59901a.js +2 -0
- package/public/select.cb638be6656d02a558f2690acd59901a.js.LICENSE.txt +24 -0
- package/public/select.html +26 -0
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
const startWhistle = require('whistle');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const HOME_DIR = require('os').homedir();
|
|
4
|
+
const { CONFIG_DATA_TYPE, getPort, getNohostPluginsPath, loadModule, PLUGINS_DIR } = require('./util');
|
|
5
|
+
|
|
6
|
+
const WHISTLE_SECURE_FILTER = path.resolve(HOME_DIR, 'nohost/whistleSecureFilter.js');
|
|
7
|
+
const IDLE_TIMEOUT = 6 * 60 * 1000;
|
|
8
|
+
|
|
9
|
+
let index = 0;
|
|
10
|
+
let updatedIndex;
|
|
11
|
+
let curEnvList = [];
|
|
12
|
+
let config;
|
|
13
|
+
let proxy;
|
|
14
|
+
let isHeadless;
|
|
15
|
+
|
|
16
|
+
const getShadowRules = (options) => {
|
|
17
|
+
const shadowRules = isHeadless ? '* responseFor://name=x-upstream' : '* responseFor://name=x-upstream,req.x-whistle-nohost-env';
|
|
18
|
+
return options.defaultRules ? `${options.defaultRules}\n${shadowRules}` : shadowRules;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const syncData = () => {
|
|
22
|
+
let updateTimer;
|
|
23
|
+
let checkTimer;
|
|
24
|
+
process.on('data', (data) => {
|
|
25
|
+
const type = data && data.type;
|
|
26
|
+
if (type !== CONFIG_DATA_TYPE) {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
if (data.index >= 0) {
|
|
30
|
+
updatedIndex = data.index;
|
|
31
|
+
if (updatedIndex === index) {
|
|
32
|
+
clearTimeout(checkTimer);
|
|
33
|
+
checkTimer = null;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
const shadowRules = getShadowRules(data);
|
|
37
|
+
if (config && shadowRules !== config.shadowRules) {
|
|
38
|
+
config.shadowRules = shadowRules;
|
|
39
|
+
proxy.rulesUtil.parseRules();
|
|
40
|
+
}
|
|
41
|
+
proxy.setAuth(data);
|
|
42
|
+
});
|
|
43
|
+
const updateRules = () => {
|
|
44
|
+
process.sendData({
|
|
45
|
+
index,
|
|
46
|
+
envList: proxy.rulesUtil.rules.list(),
|
|
47
|
+
type: CONFIG_DATA_TYPE,
|
|
48
|
+
runtimeInfo: proxy.getRuntimeInfo(),
|
|
49
|
+
});
|
|
50
|
+
updateTimer = null;
|
|
51
|
+
clearTimeout(checkTimer);
|
|
52
|
+
checkTimer = setTimeout(() => {
|
|
53
|
+
checkTimer = null;
|
|
54
|
+
if (updatedIndex !== index) {
|
|
55
|
+
updateRules();
|
|
56
|
+
}
|
|
57
|
+
}, 1000);
|
|
58
|
+
};
|
|
59
|
+
proxy.on('rulesDataChange', () => {
|
|
60
|
+
++index;
|
|
61
|
+
updateTimer = updateTimer || setTimeout(updateRules, 300);
|
|
62
|
+
});
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
module.exports = (options, callback) => {
|
|
66
|
+
const projectPluginsPath = [
|
|
67
|
+
path.join(__dirname, '../../account'),
|
|
68
|
+
path.join(__dirname, '../../../../node_modules'),
|
|
69
|
+
];
|
|
70
|
+
const username = options.value || '$';
|
|
71
|
+
const isRulesMode = username === '$';
|
|
72
|
+
const oldPluginsPath = [
|
|
73
|
+
path.join(PLUGINS_DIR, `whistle.nohost/${username}/lib/node_modules`),
|
|
74
|
+
path.join(PLUGINS_DIR, `whistle.nohost/${username}/node_modules`),
|
|
75
|
+
path.join(PLUGINS_DIR, 'whistle.nohost/lib/node_modules'),
|
|
76
|
+
path.join(PLUGINS_DIR, 'whistle.nohost/node_modules'),
|
|
77
|
+
];
|
|
78
|
+
const accountPluginsPath = [
|
|
79
|
+
path.join(getNohostPluginsPath(), `account_plugins/${username}`),
|
|
80
|
+
];
|
|
81
|
+
const customPluginsPath = [
|
|
82
|
+
path.join(getNohostPluginsPath(), 'worker_plugins'),
|
|
83
|
+
];
|
|
84
|
+
|
|
85
|
+
isHeadless = /^\$\d+/.test(username);
|
|
86
|
+
let mode = 'multiEnv|disableUpdateTips|keepXFF|x-forwarded-proto';
|
|
87
|
+
if (options.worker) {
|
|
88
|
+
mode = `${mode}|plugins|hideLeftMenu`;
|
|
89
|
+
}
|
|
90
|
+
if (process.env.PFORK_MODE === 'bind') {
|
|
91
|
+
projectPluginsPath.unshift(process.cwd());
|
|
92
|
+
}
|
|
93
|
+
getPort((port) => {
|
|
94
|
+
proxy = startWhistle({
|
|
95
|
+
port,
|
|
96
|
+
host: '127.0.0.1',
|
|
97
|
+
cmdName: 'n2',
|
|
98
|
+
authKey: options.authKey,
|
|
99
|
+
encrypted: true,
|
|
100
|
+
storage: options.storage,
|
|
101
|
+
baseDir: process.env.NOHOST_BADE_DIR,
|
|
102
|
+
username,
|
|
103
|
+
password: isRulesMode ? `${Math.random()}` : options.password,
|
|
104
|
+
guestName: isRulesMode ? '' : options.guestName,
|
|
105
|
+
realPort: options.realPort,
|
|
106
|
+
realHost: options.realHost,
|
|
107
|
+
guestPassword: options.guestPassword,
|
|
108
|
+
shadowRules: getShadowRules(options),
|
|
109
|
+
mode: `${mode}${isRulesMode ? '|rules' : ''}`,
|
|
110
|
+
secureFilter: WHISTLE_SECURE_FILTER,
|
|
111
|
+
projectPluginsPath,
|
|
112
|
+
account: username,
|
|
113
|
+
accountPluginsPath,
|
|
114
|
+
customPluginsPath,
|
|
115
|
+
pluginsPath: (options.pluginsPath || []).concat(oldPluginsPath),
|
|
116
|
+
dnsServer: options.dnsServer,
|
|
117
|
+
addon: options.accountPluginPath,
|
|
118
|
+
pluginsDataMap: {
|
|
119
|
+
storage: {
|
|
120
|
+
storageServer: options.storageServer,
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
}, () => {
|
|
124
|
+
curEnvList = proxy.rulesUtil.rules.list();
|
|
125
|
+
const {
|
|
126
|
+
REMOTE_ADDR_HEAD: remoteAddrHead,
|
|
127
|
+
REMOTE_PORT_HEAD: remotePortHead,
|
|
128
|
+
} = proxy.config;
|
|
129
|
+
setTimeout(() => callback(null, {
|
|
130
|
+
port,
|
|
131
|
+
remoteAddrHead,
|
|
132
|
+
remotePortHead,
|
|
133
|
+
envList: curEnvList,
|
|
134
|
+
}), 100);
|
|
135
|
+
});
|
|
136
|
+
let timer;
|
|
137
|
+
const exitWhistleIfIdleTimeout = () => {
|
|
138
|
+
clearTimeout(timer);
|
|
139
|
+
timer = setTimeout(() => process.exit(), IDLE_TIMEOUT);
|
|
140
|
+
};
|
|
141
|
+
exitWhistleIfIdleTimeout();
|
|
142
|
+
config = loadModule('whistle/lib/config');
|
|
143
|
+
config.baseDirHash = '';
|
|
144
|
+
proxy.on('tunnelRequest', exitWhistleIfIdleTimeout);
|
|
145
|
+
proxy.on('wsRequest', exitWhistleIfIdleTimeout);
|
|
146
|
+
proxy.on('_request', exitWhistleIfIdleTimeout);
|
|
147
|
+
syncData();
|
|
148
|
+
});
|
|
149
|
+
};
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
const p = require('pfork');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const accountMgr = require('./accountMgr');
|
|
4
|
+
const {
|
|
5
|
+
CONFIG_DATA_TYPE,
|
|
6
|
+
AUTH_KEY,
|
|
7
|
+
pluginConfig,
|
|
8
|
+
} = require('./util');
|
|
9
|
+
|
|
10
|
+
const { realPort, realHost } = pluginConfig;
|
|
11
|
+
const GUEST_AUTH = { guestName: '-' };
|
|
12
|
+
const WHISTLE_WORKER = path.join(__dirname, 'whistle.js');
|
|
13
|
+
const DELAY = 6000;
|
|
14
|
+
const UPDATE_INTERVAL = 5000;
|
|
15
|
+
const cache = {};
|
|
16
|
+
|
|
17
|
+
exports.fork = (account) => {
|
|
18
|
+
if (!account) {
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
const { name, password } = account;
|
|
22
|
+
if (cache[name]) {
|
|
23
|
+
return cache[name];
|
|
24
|
+
}
|
|
25
|
+
const getAccountData = () => {
|
|
26
|
+
const data = {
|
|
27
|
+
type: CONFIG_DATA_TYPE,
|
|
28
|
+
username: account.name,
|
|
29
|
+
password: account.password,
|
|
30
|
+
guest: accountMgr.isEnableGuest() ? GUEST_AUTH : null,
|
|
31
|
+
defaultRules: accountMgr.accountRules,
|
|
32
|
+
};
|
|
33
|
+
return data;
|
|
34
|
+
};
|
|
35
|
+
cache[name] = new Promise((resolve, reject) => {
|
|
36
|
+
const guestName = accountMgr.isEnableGuest() ? '-' : undefined;
|
|
37
|
+
p.fork({
|
|
38
|
+
authKey: AUTH_KEY,
|
|
39
|
+
password,
|
|
40
|
+
guestName,
|
|
41
|
+
defaultRules: accountMgr.accountRules,
|
|
42
|
+
storage: `whistle.nohost/${name}`,
|
|
43
|
+
script: WHISTLE_WORKER,
|
|
44
|
+
value: name,
|
|
45
|
+
realPort,
|
|
46
|
+
realHost,
|
|
47
|
+
storageServer: accountMgr.storageServer,
|
|
48
|
+
dnsServer: accountMgr.dnsServer,
|
|
49
|
+
accountPluginPath: accountMgr.accountPluginPath,
|
|
50
|
+
}, (err, result, child) => {
|
|
51
|
+
if (err) {
|
|
52
|
+
delete cache[name];
|
|
53
|
+
return reject(err);
|
|
54
|
+
}
|
|
55
|
+
resolve(result);
|
|
56
|
+
accountMgr.addEnvList(name, result.envList);
|
|
57
|
+
let timer;
|
|
58
|
+
const updateAuth = () => {
|
|
59
|
+
account = account && accountMgr.getAccount(account.name);
|
|
60
|
+
if (!account) {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
child.sendData(getAccountData());
|
|
64
|
+
timer = setTimeout(updateAuth, UPDATE_INTERVAL);
|
|
65
|
+
};
|
|
66
|
+
timer = setTimeout(updateAuth, UPDATE_INTERVAL);
|
|
67
|
+
child.on('exit', () => {
|
|
68
|
+
clearTimeout(timer);
|
|
69
|
+
delete cache[name];
|
|
70
|
+
});
|
|
71
|
+
child.on('data', (data) => {
|
|
72
|
+
const type = data && data.type;
|
|
73
|
+
if (type !== CONFIG_DATA_TYPE) {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
account = account && accountMgr.getAccount(account.name);
|
|
77
|
+
if (!account) {
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
const accountData = getAccountData();
|
|
81
|
+
accountData.index = data.index;
|
|
82
|
+
child.sendData(accountData);
|
|
83
|
+
accountMgr.addEnvList(name, data.envList);
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
return cache[name];
|
|
88
|
+
};
|
|
89
|
+
exports.kill = (name) => {
|
|
90
|
+
p.kill({
|
|
91
|
+
script: WHISTLE_WORKER,
|
|
92
|
+
value: name,
|
|
93
|
+
}, DELAY);
|
|
94
|
+
};
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const fse = require('fs-extra');
|
|
4
|
+
const Limiter = require('async-limiter');
|
|
5
|
+
const { gzip } = require('zlib');
|
|
6
|
+
const { getSessionsDir, getDate, getAccessCode } = require('./util');
|
|
7
|
+
|
|
8
|
+
const promises = {};
|
|
9
|
+
const limiter = new Limiter({ concurrency: 10 });
|
|
10
|
+
const ROUTE_RE = /^[\w.:/=+-]{1,100}$/;
|
|
11
|
+
|
|
12
|
+
const writeFile = (filepath, data) => {
|
|
13
|
+
return new Promise((resolve, reject) => {
|
|
14
|
+
fs.writeFile(filepath, data, {
|
|
15
|
+
flag: 'wx',
|
|
16
|
+
}, (err) => {
|
|
17
|
+
if (err) {
|
|
18
|
+
reject(err);
|
|
19
|
+
} else {
|
|
20
|
+
resolve();
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const createDir = (dir) => {
|
|
27
|
+
let p = promises[dir];
|
|
28
|
+
if (!p) {
|
|
29
|
+
p = fse.ensureDir(dir);
|
|
30
|
+
promises[dir] = p;
|
|
31
|
+
}
|
|
32
|
+
return p;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const compressSessions = (sessions) => {
|
|
36
|
+
return new Promise((resolve, reject) => {
|
|
37
|
+
limiter.push((done) => {
|
|
38
|
+
gzip(sessions, (err, buf) => {
|
|
39
|
+
done();
|
|
40
|
+
if (err) {
|
|
41
|
+
reject(err);
|
|
42
|
+
} else {
|
|
43
|
+
resolve(buf);
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
module.exports = async (ctx) => {
|
|
51
|
+
let { query: { username, route }, body: { name, sessions, code } } = ctx.request;
|
|
52
|
+
if (!name || !sessions || typeof sessions !== 'string') {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
code = getAccessCode(code);
|
|
56
|
+
const date = getDate();
|
|
57
|
+
const dir = getSessionsDir(username, date, code);
|
|
58
|
+
if (!dir) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
await createDir(dir);
|
|
62
|
+
try {
|
|
63
|
+
sessions = await compressSessions(sessions);
|
|
64
|
+
name = encodeURIComponent(name);
|
|
65
|
+
if (code) {
|
|
66
|
+
name = `${name}[${code}]`;
|
|
67
|
+
}
|
|
68
|
+
await writeFile(path.join(dir, `${name}[gz]`), sessions);
|
|
69
|
+
ctx.body = { ec: 0, date, username, route: route && ROUTE_RE.test(route) ? route : undefined };
|
|
70
|
+
} catch (e) {
|
|
71
|
+
ctx.body = { ec: e.code === 'EEXIST' ? 1 : 2 };
|
|
72
|
+
}
|
|
73
|
+
if (ctx.get('origin')) {
|
|
74
|
+
ctx.set('Access-Control-Allow-Origin', '*');
|
|
75
|
+
}
|
|
76
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const { getSessionsDir, getAccessCode } = require('./util');
|
|
4
|
+
|
|
5
|
+
const isGzipFile = (gzFilePath) => {
|
|
6
|
+
return new Promise((resolve) => {
|
|
7
|
+
fs.stat(gzFilePath, (err, stat) => {
|
|
8
|
+
resolve(!err && stat.isFile());
|
|
9
|
+
});
|
|
10
|
+
});
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
module.exports = async (ctx) => {
|
|
14
|
+
let { username, name, date, code } = ctx.request.query;
|
|
15
|
+
if (!name || typeof name !== 'string') {
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
code = getAccessCode(code);
|
|
19
|
+
const dir = getSessionsDir(username, date, code);
|
|
20
|
+
if (!dir) {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
name = encodeURIComponent(name);
|
|
24
|
+
if (code) {
|
|
25
|
+
name = `${name}[${code}]`;
|
|
26
|
+
}
|
|
27
|
+
const gzFilePath = path.join(dir, `${name}[gz]`);
|
|
28
|
+
const isGFile = await isGzipFile(gzFilePath);
|
|
29
|
+
if (isGFile) {
|
|
30
|
+
ctx.set('content-encoding', 'gzip');
|
|
31
|
+
}
|
|
32
|
+
ctx.body = fs.createReadStream(isGFile ? gzFilePath : path.join(dir, name));
|
|
33
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
const os = require('os');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
const USERNAME_RE = /^[a-z\d.]{1,64}$/;
|
|
5
|
+
const DATE_RE = /^\d{8}$/;
|
|
6
|
+
const ACCESS_CODE_RE = /^[a-z\d]{4}$/i;
|
|
7
|
+
const leftPad = num => (num > 9 ? num : `0${num}`);
|
|
8
|
+
let nohostPath;
|
|
9
|
+
|
|
10
|
+
const getSessionsPath = () => {
|
|
11
|
+
nohostPath = nohostPath || process.env.NOHOST_PATH || path.join(os.homedir(), '.NohostAppData');
|
|
12
|
+
return nohostPath;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const getDate = () => {
|
|
16
|
+
const now = new Date();
|
|
17
|
+
return `${now.getFullYear()}${leftPad(now.getMonth() + 1)}${leftPad(now.getDate())}`;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
exports.getDate = getDate;
|
|
21
|
+
|
|
22
|
+
const checkUsername = (username) => {
|
|
23
|
+
return username == null || username === '' || (typeof username === 'string' && USERNAME_RE.test(username));
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
exports.getSessionsDir = (username, date, encrypted) => {
|
|
27
|
+
if (!DATE_RE.test(date) || !checkUsername(username)) {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
const dir = getSessionsPath();
|
|
31
|
+
date = path.join(date, encrypted ? 'encrypted' : 'sessions');
|
|
32
|
+
return username ? path.join(dir, username, date) : path.join(dir, date);
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
exports.getAccessCode = code => (ACCESS_CODE_RE.test(code) ? code.toLowerCase() : '');
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
const { fork } = require('pfork');
|
|
2
|
+
const script = require('path').join(__dirname, 'server.js');
|
|
3
|
+
const getPort = require('../util/getPort');
|
|
4
|
+
|
|
5
|
+
const workers = [];
|
|
6
|
+
const getCreator = () => {
|
|
7
|
+
let promise;
|
|
8
|
+
return () => {
|
|
9
|
+
if (!promise) {
|
|
10
|
+
promise = new Promise((resolve, reject) => {
|
|
11
|
+
getPort((port) => {
|
|
12
|
+
fork({ value: `${port}`, script }, (err, _, child) => {
|
|
13
|
+
if (err) {
|
|
14
|
+
promise = null;
|
|
15
|
+
reject(err);
|
|
16
|
+
} else {
|
|
17
|
+
child.once('close', () => {
|
|
18
|
+
promise = null;
|
|
19
|
+
});
|
|
20
|
+
resolve(port);
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
return promise;
|
|
27
|
+
};
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
module.exports = (index) => {
|
|
31
|
+
let createServer = workers[index];
|
|
32
|
+
if (!createServer) {
|
|
33
|
+
createServer = getCreator();
|
|
34
|
+
workers[index] = getCreator();
|
|
35
|
+
}
|
|
36
|
+
return createServer();
|
|
37
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
|
|
2
|
+
const { createServer } = require('http');
|
|
3
|
+
const Koa = require('koa');
|
|
4
|
+
const onerror = require('koa-onerror');
|
|
5
|
+
const router = require('koa-router')();
|
|
6
|
+
const bodyParser = require('koa-bodyparser');
|
|
7
|
+
const setupRouter = require('./router');
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
module.exports = ({ value: port }, callback) => {
|
|
11
|
+
const server = createServer();
|
|
12
|
+
const app = new Koa();
|
|
13
|
+
app.proxy = true;
|
|
14
|
+
app.silent = true;
|
|
15
|
+
if (process.env.PFORK_MODE === 'bind') {
|
|
16
|
+
onerror(app);
|
|
17
|
+
}
|
|
18
|
+
setupRouter(router);
|
|
19
|
+
app.use(bodyParser({ formLimit: '8mb' }));
|
|
20
|
+
app.use(router.routes());
|
|
21
|
+
app.use(router.allowedMethods());
|
|
22
|
+
server.on('request', app.callback());
|
|
23
|
+
server.on('error', callback);
|
|
24
|
+
server.listen(port, '127.0.0.1', callback);
|
|
25
|
+
};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
const os = require('os');
|
|
2
|
+
const net = require('net');
|
|
3
|
+
|
|
4
|
+
const LOCALHOST = '127.0.0.1';
|
|
5
|
+
let addressList = [];
|
|
6
|
+
let serverIp;
|
|
7
|
+
|
|
8
|
+
(function updateSystyemInfo() {
|
|
9
|
+
const interfaces = os.networkInterfaces();
|
|
10
|
+
addressList = [];
|
|
11
|
+
Object.keys(interfaces).forEach((name) => {
|
|
12
|
+
const list = interfaces[name];
|
|
13
|
+
if (Array.isArray(list)) {
|
|
14
|
+
list.forEach((info) => {
|
|
15
|
+
if (!info.internal && (info.family === 'IPv4' || info.family === 4)) {
|
|
16
|
+
serverIp = info.address;
|
|
17
|
+
}
|
|
18
|
+
addressList.push(info.address.toLowerCase());
|
|
19
|
+
});
|
|
20
|
+
// 支持多网卡时,以环境变量指定 serverIp
|
|
21
|
+
const envServerIp = process.env.NOHOST_SERVER_IP;
|
|
22
|
+
if (net.isIP(envServerIp) && addressList.includes(envServerIp)) {
|
|
23
|
+
serverIp = envServerIp;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
setTimeout(updateSystyemInfo, 30000);
|
|
28
|
+
}());
|
|
29
|
+
|
|
30
|
+
exports.getAddressList = () => addressList;
|
|
31
|
+
|
|
32
|
+
const isLocalHost = (ip) => {
|
|
33
|
+
if (!ip || typeof ip !== 'string') {
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
36
|
+
return ip.length < 7 || ip === LOCALHOST;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const isLocalAddress = (address) => {
|
|
40
|
+
if (isLocalHost(address)) {
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
if (address === '0:0:0:0:0:0:0:1') {
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
address = address.toLowerCase();
|
|
47
|
+
if (address[0] === '[') {
|
|
48
|
+
address = address.slice(1, -1);
|
|
49
|
+
}
|
|
50
|
+
return addressList.indexOf(address) !== -1;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
exports.isLocalAddress = isLocalAddress;
|
|
54
|
+
|
|
55
|
+
exports.getServerIp = () => serverIp;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
const http = require('http');
|
|
2
|
+
|
|
3
|
+
let curPort = 30013;
|
|
4
|
+
|
|
5
|
+
const getPort = (callback) => {
|
|
6
|
+
const server = http.createServer();
|
|
7
|
+
server.on('error', () => {
|
|
8
|
+
if (++curPort % 5 === 0) {
|
|
9
|
+
++curPort;
|
|
10
|
+
}
|
|
11
|
+
getPort(callback);
|
|
12
|
+
});
|
|
13
|
+
server.listen(curPort, '127.0.0.1', () => {
|
|
14
|
+
server.removeAllListeners();
|
|
15
|
+
server.close(() => callback(curPort));
|
|
16
|
+
});
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
module.exports = getPort;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
const crypto = require('crypto');
|
|
2
|
+
const getAuth = require('basic-auth');
|
|
3
|
+
|
|
4
|
+
const ENV_MAX_AGE = 60 * 60 * 24 * 3;
|
|
5
|
+
|
|
6
|
+
const shasum = (str) => {
|
|
7
|
+
if (typeof str !== 'string') {
|
|
8
|
+
str = '';
|
|
9
|
+
}
|
|
10
|
+
const result = crypto.createHash('sha1');
|
|
11
|
+
result.update(str);
|
|
12
|
+
return result.digest('hex');
|
|
13
|
+
};
|
|
14
|
+
exports.shasum = shasum;
|
|
15
|
+
|
|
16
|
+
const getLoginKey = (ctx, username, password) => {
|
|
17
|
+
const ip = ctx.ip || '127.0.0.1';
|
|
18
|
+
return shasum(`${username || ''}\n${password || ''}\n${ip}`);
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
exports.checkLogin = (ctx, authConf) => {
|
|
22
|
+
const {
|
|
23
|
+
username,
|
|
24
|
+
password,
|
|
25
|
+
nameKey,
|
|
26
|
+
authKey,
|
|
27
|
+
} = authConf;
|
|
28
|
+
|
|
29
|
+
if (!username || !password) {
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
const curName = ctx.cookies.get(nameKey);
|
|
33
|
+
const lkey = ctx.cookies.get(authKey);
|
|
34
|
+
const correctKey = getLoginKey(ctx, username, password);
|
|
35
|
+
if (curName === username && correctKey === lkey) {
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const { name, pass } = getAuth(ctx.req) || {};
|
|
40
|
+
if (name === username && shasum(pass) === password) {
|
|
41
|
+
const options = {
|
|
42
|
+
expires: new Date(Date.now() + (ENV_MAX_AGE * 1000)),
|
|
43
|
+
path: '/',
|
|
44
|
+
};
|
|
45
|
+
ctx.cookies.set(nameKey, username, options);
|
|
46
|
+
ctx.cookies.set(authKey, correctKey, options);
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
ctx.status = 401;
|
|
51
|
+
ctx.set('WWW-Authenticate', ' Basic realm=User Login');
|
|
52
|
+
ctx.set('Content-Type', 'text/html; charset=utf8');
|
|
53
|
+
ctx.body = 'Access denied, please <a href="javascript:;" onclick="location.reload()">try again</a>.';
|
|
54
|
+
return false;
|
|
55
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
|
|
2
|
+
const isBaseUrl = d => /^[\w-]+(?:\.[\w-]+){1,}$/.test(d);
|
|
3
|
+
const SEP_RE = /\s*[,\s]\s*/;
|
|
4
|
+
let domainList = [];
|
|
5
|
+
let curDomain;
|
|
6
|
+
|
|
7
|
+
const parseDomain = (domainStr) => {
|
|
8
|
+
if (domainStr !== curDomain) {
|
|
9
|
+
curDomain = domainStr;
|
|
10
|
+
domainList = curDomain.trim().split(SEP_RE).filter(isBaseUrl);
|
|
11
|
+
}
|
|
12
|
+
return domainList;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
module.exports = (str) => {
|
|
16
|
+
return !str || typeof str !== 'string' ? [] : parseDomain(str);
|
|
17
|
+
};
|