hfs 0.47.2 → 0.48.0-alpha1
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/admin/assets/index-12bfad23.js +520 -0
- package/admin/assets/{index-62247236.css → index-5f04953d.css} +1 -1
- package/{frontend/assets/sha512-108e3b0a.js → admin/assets/sha512-888f0eaa.js} +1 -1
- package/admin/index.html +2 -2
- package/frontend/assets/index-05dfd82f.css +1 -0
- package/frontend/assets/{index-7eec0199.js → index-12411ff6.js} +15 -15
- package/{admin/assets/sha512-0935647a.js → frontend/assets/sha512-549e6c3a.js} +1 -1
- package/frontend/index.html +2 -2
- package/package.json +20 -13
- package/plugins/vhosting/plugin.js +15 -15
- package/src/adminApis.js +4 -2
- package/src/api.auth.js +0 -3
- package/src/api.file_list.js +8 -7
- package/src/api.lang.js +1 -1
- package/src/api.net.js +133 -0
- package/src/api.plugins.js +31 -36
- package/src/api.vfs.js +1 -1
- package/src/apiMiddleware.js +22 -20
- package/src/config.js +2 -16
- package/src/const.js +4 -2
- package/src/cross.js +221 -0
- package/src/customHtml.js +7 -22
- package/src/debounceAsync.js +12 -9
- package/src/frontEndApis.js +2 -4
- package/src/github.js +86 -40
- package/src/langs/embedded.js +2 -1
- package/src/langs/hfs-lang-de.json +131 -0
- package/src/langs/hfs-lang-it.json +1 -1
- package/src/listen.js +49 -40
- package/src/log.js +3 -3
- package/src/middlewares.js +9 -6
- package/src/misc.js +33 -137
- package/src/plugins.js +46 -26
- package/src/update.js +1 -1
- package/src/util-http.js +11 -8
- package/src/util-os.js +1 -1
- package/admin/assets/index-ca5ac85e.js +0 -518
- package/frontend/assets/index-0ea37f5f.css +0 -1
- package/src/util-generators.js +0 -31
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{g as OF,c as UF}from"./index-
|
|
1
|
+
import{g as OF,c as UF}from"./index-12411ff6.js";function gF(sF,hF){for(var eF=0;eF<hF.length;eF++){const tF=hF[eF];if(typeof tF!="string"&&!Array.isArray(tF)){for(const w in tF)if(w!=="default"&&!(w in sF)){const lF=Object.getOwnPropertyDescriptor(tF,w);lF&&Object.defineProperty(sF,w,lF.get?lF:{enumerable:!0,get:()=>tF[w]})}}}return Object.freeze(Object.defineProperty(sF,Symbol.toStringTag,{value:"Module"}))}var dF={exports:{}};/*
|
|
2
2
|
* [js-sha512]{@link https://github.com/emn178/js-sha512}
|
|
3
3
|
*
|
|
4
4
|
* @version 0.8.0
|
package/frontend/index.html
CHANGED
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=0" />
|
|
6
6
|
<link href="/fontello.css" rel="stylesheet" />
|
|
7
7
|
|
|
8
|
-
<script type="module" crossorigin src="/assets/index-
|
|
9
|
-
<link rel="stylesheet" href="/assets/index-
|
|
8
|
+
<script type="module" crossorigin src="/assets/index-12411ff6.js"></script>
|
|
9
|
+
<link rel="stylesheet" href="/assets/index-05dfd82f.css">
|
|
10
10
|
</head>
|
|
11
11
|
<body>
|
|
12
12
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hfs",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.48.0-alpha1",
|
|
4
4
|
"description": "HTTP File Server",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"file server",
|
|
@@ -9,7 +9,12 @@
|
|
|
9
9
|
"homepage": "https://rejetto.com/hfs",
|
|
10
10
|
"license": "GPL-3.0",
|
|
11
11
|
"author": "Massimo Melina <a@rejetto.com>",
|
|
12
|
-
"workspaces": [
|
|
12
|
+
"workspaces": [
|
|
13
|
+
"admin",
|
|
14
|
+
"frontend",
|
|
15
|
+
"shared",
|
|
16
|
+
"mui-grid-form"
|
|
17
|
+
],
|
|
13
18
|
"scripts": {
|
|
14
19
|
"watch-server": "cross-env DEV=1 nodemon --ignore tests/ --watch src -e ts,tsx --exec ts-node src",
|
|
15
20
|
"watch-server-proxied": "cross-env FRONTEND_PROXY=3005 ADMIN_PROXY=3006 npm run watch-server",
|
|
@@ -25,10 +30,10 @@
|
|
|
25
30
|
"pub": "cd dist && npm publish",
|
|
26
31
|
"dist": "npm run build-all && npm run dist-bin",
|
|
27
32
|
"dist-bin": "npm run dist-modules && cd dist && pkg . --public -C gzip && mv -f hfs-win-x64.exe hfs.exe && zip hfs-windows.zip hfs.exe -r plugins && cp -f hfs-linux-x64 hfs && zip hfs-linux.zip hfs -r plugins && cp -f hfs-macos-x64 hfs && zip hfs-mac.zip hfs -r plugins && cp -f hfs-macos-arm64 hfs && zip hfs-mac-arm.zip hfs -r plugins && rm hfs",
|
|
28
|
-
"dist-modules": "cp package*.json dist && cd dist && npm ci --omit=dev && npm run dist-crclib && rm package-lock.json && cd .. && node prune_modules",
|
|
33
|
+
"dist-modules": "cp package*.json central.json dist && cd dist && npm ci --omit=dev && npm run dist-crclib && rm package-lock.json && cd .. && node prune_modules",
|
|
29
34
|
"dist-crclib": "npm i -f --no-save --omit=dev @node-rs/crc32-win32-x64-msvc @node-rs/crc32-darwin-arm64 @node-rs/crc32-darwin-x64 @node-rs/crc32-linux-x64-gnu",
|
|
30
|
-
"dist-win": "
|
|
31
|
-
"dist-mac": "
|
|
35
|
+
"dist-win": "npm run dist-modules && cd dist && pkg . --public -C gzip -t node16-win-x64",
|
|
36
|
+
"dist-mac": "npm run dist-modules && cd dist && pkg . --public -C gzip -t node16-macos-arm64",
|
|
32
37
|
"dist-node": "npm run dist-modules && cd dist && zip hfs-node.zip -r * -x *.zip *.exe hfs-* *.log logs"
|
|
33
38
|
},
|
|
34
39
|
"engines": {
|
|
@@ -49,14 +54,15 @@
|
|
|
49
54
|
},
|
|
50
55
|
"pkg": {
|
|
51
56
|
"assets": [
|
|
57
|
+
"central.json",
|
|
52
58
|
"admin/**/*",
|
|
53
59
|
"frontend/**/*"
|
|
54
60
|
],
|
|
55
61
|
"targets": [
|
|
56
|
-
"
|
|
57
|
-
"
|
|
58
|
-
"
|
|
59
|
-
"
|
|
62
|
+
"node18-win-x64",
|
|
63
|
+
"node18-mac-x64",
|
|
64
|
+
"node18-mac-arm64",
|
|
65
|
+
"node18-linux-x64"
|
|
60
66
|
]
|
|
61
67
|
},
|
|
62
68
|
"dependencies": {
|
|
@@ -64,10 +70,9 @@
|
|
|
64
70
|
"@node-rs/crc32": "^1.6.0",
|
|
65
71
|
"basic-auth": "^2.0.1",
|
|
66
72
|
"buffer-crc32": "^0.2.13",
|
|
67
|
-
"cidr-tools": "^4.3.0",
|
|
68
73
|
"fast-glob": "^3.2.7",
|
|
69
74
|
"find-process": "^1.4.7",
|
|
70
|
-
"formidable": "^
|
|
75
|
+
"formidable": "^3.5.1",
|
|
71
76
|
"fs-x-attributes": "^1.0.2",
|
|
72
77
|
"koa": "^2.13.4",
|
|
73
78
|
"koa-compress": "^5.1.0",
|
|
@@ -76,6 +81,8 @@
|
|
|
76
81
|
"limiter": "^2.1.0",
|
|
77
82
|
"lodash": "^4.17.21",
|
|
78
83
|
"minimist": "^1.2.6",
|
|
84
|
+
"nat-upnp": "github:kaden-sharpin/node-nat-upnp",
|
|
85
|
+
"node-html-parser": "^6.1.5",
|
|
79
86
|
"open": "^8.4.0",
|
|
80
87
|
"tssrp6a": "^3.0.0",
|
|
81
88
|
"unzip-stream": "^0.3.1",
|
|
@@ -85,7 +92,7 @@
|
|
|
85
92
|
"devDependencies": {
|
|
86
93
|
"@types/archiver": "^5.1.1",
|
|
87
94
|
"@types/basic-auth": "^1.1.3",
|
|
88
|
-
"@types/formidable": "^
|
|
95
|
+
"@types/formidable": "^3.4.1",
|
|
89
96
|
"@types/koa": "^2.13.4",
|
|
90
97
|
"@types/koa__router": "^8.0.11",
|
|
91
98
|
"@types/koa-compress": "^4.0.3",
|
|
@@ -96,7 +103,7 @@
|
|
|
96
103
|
"@types/mime-types": "^2.1.1",
|
|
97
104
|
"@types/minimist": "^1.2.2",
|
|
98
105
|
"@types/mocha": "^9.0.0",
|
|
99
|
-
"@types/node": "^
|
|
106
|
+
"@types/node": "^18.17.14",
|
|
100
107
|
"@types/tough-cookie": "^4.0.2",
|
|
101
108
|
"@types/unzipper": "^0.10.5",
|
|
102
109
|
"axios": "^0.24.0",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
exports.description = "If you want to have different home folders, based on domain"
|
|
2
|
-
exports.version = 3.
|
|
2
|
+
exports.version = 3.2
|
|
3
3
|
exports.apiRequired = 2 // 2 is for the config 'array'
|
|
4
4
|
|
|
5
5
|
exports.config = {
|
|
@@ -27,12 +27,11 @@ exports.init = api => {
|
|
|
27
27
|
middleware(ctx) {
|
|
28
28
|
let params // undefined if we are not going to work on api parameters
|
|
29
29
|
if (ctx.path.startsWith(api.const.SPECIAL_URI)) { // special uris should be excluded...
|
|
30
|
-
// ...unless it's
|
|
31
|
-
if (!ctx.path.startsWith(api.const.API_URI)) return
|
|
30
|
+
if (!ctx.path.startsWith(api.const.API_URI)) return // ...unless it's an api
|
|
32
31
|
let { referer } = ctx.headers
|
|
33
32
|
referer &&= new URL(referer).pathname
|
|
34
33
|
if (referer?.startsWith(ctx.state.revProxyPath + api.const.ADMIN_URI)) return // exclude apis for admin-panel
|
|
35
|
-
params = ctx.params
|
|
34
|
+
params = ctx.params || ctx.query // for api we'll translate params
|
|
36
35
|
}
|
|
37
36
|
|
|
38
37
|
const hosts = api.getConfig('hosts')
|
|
@@ -46,17 +45,18 @@ exports.init = api => {
|
|
|
46
45
|
return
|
|
47
46
|
}
|
|
48
47
|
let { root='' } = row
|
|
49
|
-
if (root
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
for (const [k,v] of Object.entries(params))
|
|
58
|
-
if (k.startsWith('uri'))
|
|
59
|
-
params[k] = Array.isArray(v) ? v.map(x => root + x) : root + v
|
|
48
|
+
if (!root || root === '/') return
|
|
49
|
+
if (params === undefined) {
|
|
50
|
+
ctx.path = join(root, ctx.path)
|
|
51
|
+
return
|
|
52
|
+
}
|
|
53
|
+
for (const [k,v] of Object.entries(params))
|
|
54
|
+
if (k.startsWith('uri'))
|
|
55
|
+
params[k] = Array.isArray(v) ? v.map(x => join(root, x)) : join(root, v)
|
|
60
56
|
}
|
|
61
57
|
}
|
|
62
58
|
}
|
|
59
|
+
|
|
60
|
+
function join(a, b) {
|
|
61
|
+
return a + (b && b[0] !== '/' ? '/' : '') + b
|
|
62
|
+
}
|
package/src/adminApis.js
CHANGED
|
@@ -37,6 +37,7 @@ const api_accounts_1 = __importDefault(require("./api.accounts"));
|
|
|
37
37
|
const api_plugins_1 = __importDefault(require("./api.plugins"));
|
|
38
38
|
const api_monitor_1 = __importDefault(require("./api.monitor"));
|
|
39
39
|
const api_lang_1 = __importDefault(require("./api.lang"));
|
|
40
|
+
const api_net_1 = __importDefault(require("./api.net"));
|
|
40
41
|
const connections_1 = require("./connections");
|
|
41
42
|
const misc_1 = require("./misc");
|
|
42
43
|
const events_1 = __importDefault(require("./events"));
|
|
@@ -58,6 +59,7 @@ exports.adminApis = {
|
|
|
58
59
|
...api_plugins_1.default,
|
|
59
60
|
...api_monitor_1.default,
|
|
60
61
|
...api_lang_1.default,
|
|
62
|
+
...api_net_1.default,
|
|
61
63
|
async set_config({ values: v }) {
|
|
62
64
|
var _a;
|
|
63
65
|
if (v) {
|
|
@@ -104,7 +106,7 @@ exports.adminApis = {
|
|
|
104
106
|
apiVersion: const_1.API_VERSION,
|
|
105
107
|
compatibleApiVersion: const_1.COMPATIBLE_API_VERSION,
|
|
106
108
|
...await (0, listen_1.getServerStatus)(),
|
|
107
|
-
urls: (0, listen_1.getUrls)(),
|
|
109
|
+
urls: await (0, listen_1.getUrls)(),
|
|
108
110
|
baseUrl: middlewares_1.baseUrl.get(),
|
|
109
111
|
updatePossible: !(0, update_1.updateSupported)() ? false : await (0, update_1.localUpdateAvailable)() ? 'local' : true,
|
|
110
112
|
proxyDetected: (0, middlewares_1.getProxyDetected)(),
|
|
@@ -185,7 +187,7 @@ for (const [k, was] of Object.entries(exports.adminApis))
|
|
|
185
187
|
return new apiMiddleware_1.ApiError(const_1.HTTP_FORBIDDEN);
|
|
186
188
|
if (ctxAdminAccess(ctx))
|
|
187
189
|
return was(params, ctx);
|
|
188
|
-
const props = {
|
|
190
|
+
const props = { possible: anyAccountCanLoginAdmin() };
|
|
189
191
|
return ctx.headers.accept === 'text/event-stream'
|
|
190
192
|
? new apiMiddleware_1.SendListReadable({ doAtStart: x => x.error(const_1.HTTP_UNAUTHORIZED, true, props) })
|
|
191
193
|
: new apiMiddleware_1.ApiError(const_1.HTTP_UNAUTHORIZED, props);
|
package/src/api.auth.js
CHANGED
|
@@ -7,7 +7,6 @@ const crypt_1 = require("./crypt");
|
|
|
7
7
|
const apiMiddleware_1 = require("./apiMiddleware");
|
|
8
8
|
const tssrp6a_1 = require("tssrp6a");
|
|
9
9
|
const const_1 = require("./const");
|
|
10
|
-
const misc_1 = require("./misc");
|
|
11
10
|
const api_helpers_1 = require("./api.helpers");
|
|
12
11
|
const adminApis_1 = require("./adminApis");
|
|
13
12
|
const middlewares_1 = require("./middlewares");
|
|
@@ -22,12 +21,10 @@ async function loggedIn(ctx, username) {
|
|
|
22
21
|
return ctx.throw(const_1.HTTP_SERVER_ERROR, 'session');
|
|
23
22
|
if (username === false) {
|
|
24
23
|
delete s.username;
|
|
25
|
-
ctx.cookies.set('csrf', '');
|
|
26
24
|
return;
|
|
27
25
|
}
|
|
28
26
|
s.username = (0, perm_1.normalizeUsername)(username);
|
|
29
27
|
await (0, middlewares_1.prepareState)(ctx, async () => { }); // updating the state is necessary to send complete session data so that frontend shows admin button
|
|
30
|
-
ctx.cookies.set('csrf', (0, misc_1.randomId)(), { signed: false, httpOnly: false });
|
|
31
28
|
}
|
|
32
29
|
function makeExp() {
|
|
33
30
|
return !keepSessionAlive.get() ? undefined
|
package/src/api.file_list.js
CHANGED
|
@@ -4,7 +4,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
5
|
};
|
|
6
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
-
exports.
|
|
7
|
+
exports.get_file_list = void 0;
|
|
8
8
|
const vfs_1 = require("./vfs");
|
|
9
9
|
const apiMiddleware_1 = require("./apiMiddleware");
|
|
10
10
|
const promises_1 = require("fs/promises");
|
|
@@ -12,9 +12,10 @@ const plugins_1 = require("./plugins");
|
|
|
12
12
|
const misc_1 = require("./misc");
|
|
13
13
|
const lodash_1 = __importDefault(require("lodash"));
|
|
14
14
|
const const_1 = require("./const");
|
|
15
|
-
const
|
|
15
|
+
const get_file_list = async ({ uri, offset, limit, search, c }, ctx) => {
|
|
16
|
+
var _a;
|
|
16
17
|
const node = await (0, vfs_1.urlToNode)(uri || '/', ctx);
|
|
17
|
-
const list = new apiMiddleware_1.SendListReadable();
|
|
18
|
+
const list = ctx.get('accept') === 'text/event-stream' ? new apiMiddleware_1.SendListReadable() : undefined;
|
|
18
19
|
if (!node)
|
|
19
20
|
return fail(const_1.HTTP_NOT_FOUND);
|
|
20
21
|
if ((0, vfs_1.statusCodeForMissingPerm)(node, 'can_list', ctx))
|
|
@@ -22,7 +23,7 @@ const file_list = async ({ uri, offset, limit, search, c, sse }, ctx) => {
|
|
|
22
23
|
if ((0, misc_1.dirTraversal)(search))
|
|
23
24
|
return fail(const_1.HTTP_FOOL);
|
|
24
25
|
if (node.default)
|
|
25
|
-
return (
|
|
26
|
+
return ((_a = list === null || list === void 0 ? void 0 : list.custom) !== null && _a !== void 0 ? _a : lodash_1.default.identity)({
|
|
26
27
|
redirect: uri // tell the browser to access the folder (instead of using this api), so it will get the default file
|
|
27
28
|
});
|
|
28
29
|
if (!await (0, vfs_1.nodeIsDirectory)(node))
|
|
@@ -35,7 +36,7 @@ const file_list = async ({ uri, offset, limit, search, c, sse }, ctx) => {
|
|
|
35
36
|
const can_upload = (0, vfs_1.hasPermission)(node, 'can_upload', ctx);
|
|
36
37
|
const can_delete = (0, vfs_1.hasPermission)(node, 'can_delete', ctx);
|
|
37
38
|
const props = { can_upload, can_delete, accept: node.accept };
|
|
38
|
-
if (!
|
|
39
|
+
if (!list)
|
|
39
40
|
return { ...props, list: await (0, misc_1.asyncGeneratorToArray)(produceEntries()) };
|
|
40
41
|
setTimeout(async () => {
|
|
41
42
|
if (can_upload || can_delete)
|
|
@@ -46,7 +47,7 @@ const file_list = async ({ uri, offset, limit, search, c, sse }, ctx) => {
|
|
|
46
47
|
});
|
|
47
48
|
return list;
|
|
48
49
|
function fail(code = ctx.status) {
|
|
49
|
-
if (!
|
|
50
|
+
if (!list)
|
|
50
51
|
return new apiMiddleware_1.ApiError(code);
|
|
51
52
|
list.error(code, true);
|
|
52
53
|
return list;
|
|
@@ -84,7 +85,7 @@ const file_list = async ({ uri, offset, limit, search, c, sse }, ctx) => {
|
|
|
84
85
|
}
|
|
85
86
|
}
|
|
86
87
|
};
|
|
87
|
-
exports.
|
|
88
|
+
exports.get_file_list = get_file_list;
|
|
88
89
|
async function nodeToDirEntry(ctx, node) {
|
|
89
90
|
let { source, default: def } = node;
|
|
90
91
|
const name = (0, vfs_1.getNodeName)(node);
|
package/src/api.lang.js
CHANGED
|
@@ -13,7 +13,7 @@ const misc_1 = require("./misc");
|
|
|
13
13
|
const lang_1 = require("./lang");
|
|
14
14
|
const embedded_1 = __importDefault(require("./langs/embedded"));
|
|
15
15
|
const apis = {
|
|
16
|
-
|
|
16
|
+
get_langs() {
|
|
17
17
|
return new apiMiddleware_1.SendListReadable({
|
|
18
18
|
doAtStart: async (list) => {
|
|
19
19
|
for await (let name of fast_glob_1.default.stream((0, lang_1.code2file)('*'))) {
|
package/src/api.net.js
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// This file is part of HFS - Copyright 2021-2023, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt
|
|
3
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
|
+
};
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.externalIp = void 0;
|
|
8
|
+
const apiMiddleware_1 = require("./apiMiddleware");
|
|
9
|
+
const nat_upnp_1 = require("nat-upnp");
|
|
10
|
+
const const_1 = require("./const");
|
|
11
|
+
const axios_1 = __importDefault(require("axios"));
|
|
12
|
+
const node_html_parser_1 = require("node-html-parser");
|
|
13
|
+
const lodash_1 = __importDefault(require("lodash"));
|
|
14
|
+
const listen_1 = require("./listen");
|
|
15
|
+
const github_1 = require("./github");
|
|
16
|
+
const util_http_1 = require("./util-http");
|
|
17
|
+
const child_process_1 = require("child_process");
|
|
18
|
+
const misc_1 = require("./misc");
|
|
19
|
+
const client = new nat_upnp_1.Client({ timeout: 5000 });
|
|
20
|
+
const original = client.getGateway;
|
|
21
|
+
// other client methods call getGateway too, so this will ensure they reuse this same result
|
|
22
|
+
client.getGateway = function getGatewayCaching() {
|
|
23
|
+
const promise = original.apply(client);
|
|
24
|
+
client.getGateway = () => promise; // multiple callings = same job
|
|
25
|
+
promise.then(() => console.debug('caching gateway'), // store in cache only if successful.
|
|
26
|
+
() => client.getGateway = getGatewayCaching); // failed, try again
|
|
27
|
+
return promise;
|
|
28
|
+
};
|
|
29
|
+
client.getGateway();
|
|
30
|
+
exports.externalIp = Promise.resolve(''); // poll external ip
|
|
31
|
+
(0, misc_1.repeat)(10 * misc_1.MINUTE, () => exports.externalIp = client.getPublicIp().catch(() => exports.externalIp));
|
|
32
|
+
const getNatInfo = (0, misc_1.debounceAsync)(async () => {
|
|
33
|
+
var _a, _b;
|
|
34
|
+
const gettingIp = getPublicIp(); // don't wait, do it in parallel
|
|
35
|
+
const res = await client.getGateway().catch(() => null);
|
|
36
|
+
const status = await (0, listen_1.getServerStatus)();
|
|
37
|
+
const mappings = res && await client.getMappings().catch(() => null);
|
|
38
|
+
console.debug('mappings found', mappings);
|
|
39
|
+
const gatewayIp = res ? new URL(res.gateway.description).hostname : await findGateway().catch(() => null);
|
|
40
|
+
const localIp = (res === null || res === void 0 ? void 0 : res.address) || (await (0, listen_1.getIps)())[0];
|
|
41
|
+
const internalPort = ((_a = status === null || status === void 0 ? void 0 : status.https) === null || _a === void 0 ? void 0 : _a.listening) && status.https.port || ((_b = status === null || status === void 0 ? void 0 : status.http) === null || _b === void 0 ? void 0 : _b.listening) && status.http.port;
|
|
42
|
+
const mapped = lodash_1.default.find(mappings, x => x.private.host === localIp && x.private.port === internalPort || x.description === 'hfs');
|
|
43
|
+
console.debug('responding');
|
|
44
|
+
return {
|
|
45
|
+
upnp: Boolean(res),
|
|
46
|
+
localIp,
|
|
47
|
+
gatewayIp,
|
|
48
|
+
publicIp: await gettingIp || await exports.externalIp,
|
|
49
|
+
externalIp: await exports.externalIp,
|
|
50
|
+
mapped,
|
|
51
|
+
internalPort,
|
|
52
|
+
externalPort: mapped === null || mapped === void 0 ? void 0 : mapped.public.port,
|
|
53
|
+
};
|
|
54
|
+
});
|
|
55
|
+
async function getPublicIp() {
|
|
56
|
+
const prjInfo = await (0, github_1.getProjectInfo)();
|
|
57
|
+
for (const urls of lodash_1.default.chunk(lodash_1.default.shuffle(prjInfo.publicIpServices), 2)) // small parallelization
|
|
58
|
+
try {
|
|
59
|
+
return await Promise.any(urls.map(url => (0, util_http_1.httpString)(url).then(res => {
|
|
60
|
+
var _a;
|
|
61
|
+
const ip = (_a = res.body) === null || _a === void 0 ? void 0 : _a.trim();
|
|
62
|
+
if (!/[.:0-9a-fA-F]/.test(ip))
|
|
63
|
+
throw Error("bad result: " + ip);
|
|
64
|
+
return ip;
|
|
65
|
+
})));
|
|
66
|
+
}
|
|
67
|
+
catch (e) {
|
|
68
|
+
console.debug(String(e));
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
function findGateway() {
|
|
72
|
+
return new Promise((resolve, reject) => (0, child_process_1.exec)(const_1.IS_WINDOWS || const_1.IS_MAC ? 'netstat -rn' : 'route -n', (err, out) => {
|
|
73
|
+
var _a;
|
|
74
|
+
if (err)
|
|
75
|
+
return reject(err);
|
|
76
|
+
const re = const_1.IS_WINDOWS ? /(?:0\.0\.0\.0 +){2}([\d.]+)/ : const_1.IS_MAC ? /default +([\d.]+)/ : /^0\.0\.0\.0 +([\d.]+)/;
|
|
77
|
+
resolve((_a = re.exec(out)) === null || _a === void 0 ? void 0 : _a[1]);
|
|
78
|
+
}));
|
|
79
|
+
}
|
|
80
|
+
const apis = {
|
|
81
|
+
get_nat: getNatInfo,
|
|
82
|
+
async map_port({ external }) {
|
|
83
|
+
const { gatewayIp, mapped, internalPort } = await getNatInfo();
|
|
84
|
+
if (!gatewayIp)
|
|
85
|
+
return new apiMiddleware_1.ApiError(const_1.HTTP_SERVICE_UNAVAILABLE, 'upnp failed');
|
|
86
|
+
if (!internalPort)
|
|
87
|
+
return new apiMiddleware_1.ApiError(const_1.HTTP_FAILED_DEPENDENCY, 'no internal port');
|
|
88
|
+
if (mapped)
|
|
89
|
+
try {
|
|
90
|
+
await client.removeMapping({ public: { host: '', port: mapped.public.port } });
|
|
91
|
+
}
|
|
92
|
+
catch (e) {
|
|
93
|
+
return new apiMiddleware_1.ApiError(const_1.HTTP_SERVER_ERROR, 'removeMapping failed: ' + String(e));
|
|
94
|
+
}
|
|
95
|
+
if (external) // must use the object form of 'public' to workaround a bug of the library
|
|
96
|
+
await client.createMapping({ private: internalPort, public: { host: '', port: external }, description: 'hfs', ttl: 0 });
|
|
97
|
+
return {};
|
|
98
|
+
},
|
|
99
|
+
async check_server() {
|
|
100
|
+
const { publicIp, internalPort, externalPort } = await getNatInfo();
|
|
101
|
+
if (!publicIp)
|
|
102
|
+
return new apiMiddleware_1.ApiError(const_1.HTTP_SERVICE_UNAVAILABLE, 'cannot detect public ip');
|
|
103
|
+
if (!internalPort)
|
|
104
|
+
return new apiMiddleware_1.ApiError(const_1.HTTP_FAILED_DEPENDENCY, 'no internal port');
|
|
105
|
+
const prjInfo = await (0, github_1.getProjectInfo)();
|
|
106
|
+
const port = externalPort || internalPort;
|
|
107
|
+
console.log(`checking server ${publicIp}:${port}`);
|
|
108
|
+
for (const services of lodash_1.default.chunk(lodash_1.default.shuffle(prjInfo.checkServerServices), 2)) {
|
|
109
|
+
try {
|
|
110
|
+
return Promise.any(services.map(async (svc) => {
|
|
111
|
+
var _a, _b;
|
|
112
|
+
const service = new URL(svc.url).hostname;
|
|
113
|
+
console.log('trying service', service);
|
|
114
|
+
const api = axios_1.default[svc.method];
|
|
115
|
+
const body = ((_a = svc.body) === null || _a === void 0 ? void 0 : _a.replace('$IP', publicIp).replace('$PORT', String(port))) || '';
|
|
116
|
+
const res = await api(svc.url, body, { headers: svc.headers });
|
|
117
|
+
const parsed = (_b = (0, node_html_parser_1.parse)(res.data).querySelector(svc.selector)) === null || _b === void 0 ? void 0 : _b.innerText;
|
|
118
|
+
if (!parsed)
|
|
119
|
+
throw console.debug('empty:' + service);
|
|
120
|
+
const success = new RegExp(svc.regexpSuccess).test(parsed);
|
|
121
|
+
const failure = new RegExp(svc.regexpFailure).test(parsed);
|
|
122
|
+
if (success === failure)
|
|
123
|
+
throw console.debug('inconsistent:' + service); // this result cannot be trusted
|
|
124
|
+
console.debug(service, 'responded', success);
|
|
125
|
+
return { success, service };
|
|
126
|
+
}));
|
|
127
|
+
}
|
|
128
|
+
catch (_a) { }
|
|
129
|
+
}
|
|
130
|
+
return new apiMiddleware_1.ApiError(const_1.HTTP_SERVICE_UNAVAILABLE, 'no service available to detect upnp mapping');
|
|
131
|
+
},
|
|
132
|
+
};
|
|
133
|
+
exports.default = apis;
|
package/src/api.plugins.js
CHANGED
|
@@ -15,7 +15,7 @@ const github_1 = require("./github");
|
|
|
15
15
|
const const_1 = require("./const");
|
|
16
16
|
const apis = {
|
|
17
17
|
get_plugins({}, ctx) {
|
|
18
|
-
const list = new apiMiddleware_1.SendListReadable({ addAtStart: [...(0, plugins_1.mapPlugins)(serialize), ...(0, plugins_1.getAvailablePlugins)()] });
|
|
18
|
+
const list = new apiMiddleware_1.SendListReadable({ addAtStart: [...(0, plugins_1.mapPlugins)(serialize), ...(0, plugins_1.getAvailablePlugins)().map(serialize)] });
|
|
19
19
|
return list.events(ctx, {
|
|
20
20
|
pluginInstalled: p => list.add(serialize(p)),
|
|
21
21
|
'pluginStarted pluginStopped pluginUpdated': p => {
|
|
@@ -27,35 +27,37 @@ const apis = {
|
|
|
27
27
|
function serialize(p) {
|
|
28
28
|
const o = 'getData' in p ? Object.assign(lodash_1.default.pick(p, ['id', 'started']), p.getData())
|
|
29
29
|
: { ...p }; // _.defaults mutates object, and we don't want that
|
|
30
|
+
if (typeof o.repo === 'object') // custom repo
|
|
31
|
+
o.repo = o.repo.web;
|
|
30
32
|
return lodash_1.default.defaults(o, { started: null, badApi: null }); // nulls should be used to be sure to overwrite previous values,
|
|
31
33
|
}
|
|
32
34
|
},
|
|
33
35
|
async get_plugin_updates() {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
36
|
+
return new apiMiddleware_1.SendListReadable({
|
|
37
|
+
async doAtStart(list) {
|
|
38
|
+
const errs = await Promise.all(lodash_1.default.map((0, github_1.getFolder2repo)(), async (repo, folder) => {
|
|
39
|
+
try {
|
|
40
|
+
if (!repo)
|
|
41
|
+
return;
|
|
42
|
+
//TODO shouldn't we consider other branches here?
|
|
43
|
+
const online = await (0, github_1.readOnlinePlugin)(repo);
|
|
44
|
+
if (!(online === null || online === void 0 ? void 0 : online.apiRequired) || online.badApi)
|
|
45
|
+
return;
|
|
46
|
+
const disk = (0, plugins_1.getPluginInfo)(folder);
|
|
47
|
+
if (online.version > disk.version)
|
|
48
|
+
list.add(online);
|
|
49
|
+
}
|
|
50
|
+
catch (err) {
|
|
51
|
+
if (err.message === '404') // the plugin is declaring a wrong repo
|
|
52
|
+
return;
|
|
53
|
+
return err.code || err.message;
|
|
54
|
+
}
|
|
55
|
+
}));
|
|
56
|
+
for (const x of lodash_1.default.uniq((0, misc_1.onlyTruthy)(errs)))
|
|
57
|
+
list.error(x);
|
|
58
|
+
list.close();
|
|
59
|
+
}
|
|
57
60
|
});
|
|
58
|
-
return list;
|
|
59
61
|
},
|
|
60
62
|
async start_plugin({ id }) {
|
|
61
63
|
if ((0, plugins_1.isPluginRunning)(id))
|
|
@@ -87,7 +89,7 @@ const apis = {
|
|
|
87
89
|
}
|
|
88
90
|
};
|
|
89
91
|
},
|
|
90
|
-
|
|
92
|
+
get_online_plugins({ text }, ctx) {
|
|
91
93
|
return new apiMiddleware_1.SendListReadable({
|
|
92
94
|
async doAtStart(list) {
|
|
93
95
|
try {
|
|
@@ -98,12 +100,7 @@ const apis = {
|
|
|
98
100
|
for await (const pl of (0, github_1.searchPlugins)(text)) {
|
|
99
101
|
const repo = pl.id;
|
|
100
102
|
if (lodash_1.default.includes(folder2repo, repo))
|
|
101
|
-
continue;
|
|
102
|
-
const folder = lodash_1.default.findKey(folder2repo, x => x === repo);
|
|
103
|
-
const installed = folder && (0, plugins_1.getPluginInfo)(folder);
|
|
104
|
-
Object.assign(pl, {
|
|
105
|
-
update: installed && installed.version < pl.version,
|
|
106
|
-
});
|
|
103
|
+
continue; // don't include installed plugins
|
|
107
104
|
list.add(pl);
|
|
108
105
|
// watch for events about this plugin, until this request is closed
|
|
109
106
|
undo.push((0, misc_1.onOff)(events_1.default, {
|
|
@@ -115,10 +112,6 @@ const apis = {
|
|
|
115
112
|
if (repo === (0, github_1.getFolder2repo)()[folder])
|
|
116
113
|
list.update({ id: repo }, { installed: false });
|
|
117
114
|
},
|
|
118
|
-
pluginUpdated: p => {
|
|
119
|
-
if (p.repo === repo)
|
|
120
|
-
list.update({ id: repo }, { update: p.version < pl.version });
|
|
121
|
-
},
|
|
122
115
|
['pluginDownload_' + repo](status) {
|
|
123
116
|
list.update({ id: repo }, { downloading: status !== null && status !== void 0 ? status : null });
|
|
124
117
|
}
|
|
@@ -161,6 +154,8 @@ const apis = {
|
|
|
161
154
|
exports.default = apis;
|
|
162
155
|
async function checkDependencies(repo, branch) {
|
|
163
156
|
const rec = await (0, github_1.readOnlinePlugin)(repo, branch);
|
|
157
|
+
if (!rec)
|
|
158
|
+
return;
|
|
164
159
|
const miss = rec.depend && rec.depend.map((dep) => {
|
|
165
160
|
const res = (0, plugins_1.findPluginByRepo)(dep.repo);
|
|
166
161
|
const error = !res ? 'missing'
|
package/src/api.vfs.js
CHANGED
|
@@ -151,7 +151,7 @@ const apis = {
|
|
|
151
151
|
path = (0, path_1.dirname)(path);
|
|
152
152
|
return { path };
|
|
153
153
|
},
|
|
154
|
-
|
|
154
|
+
get_ls({ path, files = true, fileMask }, ctx) {
|
|
155
155
|
return new apiMiddleware_1.SendListReadable({
|
|
156
156
|
async doAtStart(list) {
|
|
157
157
|
if (!path && const_1.IS_WINDOWS) {
|
package/src/apiMiddleware.js
CHANGED
|
@@ -24,14 +24,17 @@ function apiMiddleware(apis) {
|
|
|
24
24
|
return async (ctx) => {
|
|
25
25
|
if (!logApi.get())
|
|
26
26
|
ctx.state.dont_log = true;
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
27
|
+
const isPost = ctx.params;
|
|
28
|
+
const params = isPost ? ctx.params || {} : ctx.query;
|
|
29
|
+
const apiName = ctx.path;
|
|
30
|
+
console.debug('API', ctx.method, apiName, { ...params });
|
|
31
|
+
const safe = isPost && ctx.get('x-hfs-anti-csrf') // POST is safe because browser will enforce SameSite cookie
|
|
32
|
+
|| apiName.startsWith('get_'); // "get_" apis are safe because they make no change
|
|
33
|
+
if (!safe)
|
|
34
|
+
return send(const_1.HTTP_FOOL);
|
|
35
|
+
const apiFun = apis.hasOwnProperty(apiName) && apis[apiName];
|
|
36
|
+
if (!apiFun)
|
|
37
|
+
return send(const_1.HTTP_NOT_FOUND, 'invalid api');
|
|
35
38
|
// we don't rely on SameSite cookie option because it's https-only
|
|
36
39
|
let res;
|
|
37
40
|
try {
|
|
@@ -42,10 +45,9 @@ function apiMiddleware(apis) {
|
|
|
42
45
|
fixUri(params, k);
|
|
43
46
|
else if (typeof (v === null || v === void 0 ? void 0 : v[0]) === 'string')
|
|
44
47
|
v.forEach((x, i) => fixUri(v, i));
|
|
45
|
-
res =
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
o[k] = (0, misc_1.removeStarting)(ctx.state.revProxyPath, o[k]);
|
|
48
|
+
res = await apiFun(params, ctx);
|
|
49
|
+
function fixUri(obj, k) {
|
|
50
|
+
obj[k] = (0, misc_1.removeStarting)(ctx.state.revProxyPath, obj[k]);
|
|
49
51
|
}
|
|
50
52
|
}
|
|
51
53
|
catch (e) {
|
|
@@ -60,15 +62,15 @@ function apiMiddleware(apis) {
|
|
|
60
62
|
resAsReadable.destroy());
|
|
61
63
|
return;
|
|
62
64
|
}
|
|
63
|
-
if (res instanceof ApiError)
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
if (res instanceof Error) { // generic exception
|
|
68
|
-
ctx.body = res.message || String(res);
|
|
69
|
-
return ctx.status = const_1.HTTP_BAD_REQUEST;
|
|
70
|
-
}
|
|
65
|
+
if (res instanceof ApiError)
|
|
66
|
+
return send(res.status, res.message);
|
|
67
|
+
if (res instanceof Error) // generic error/exception
|
|
68
|
+
return send(const_1.HTTP_BAD_REQUEST, res.message || String(res));
|
|
71
69
|
ctx.body = res;
|
|
70
|
+
function send(status, body) {
|
|
71
|
+
ctx.body = body;
|
|
72
|
+
ctx.status = status;
|
|
73
|
+
}
|
|
72
74
|
};
|
|
73
75
|
}
|
|
74
76
|
exports.apiMiddleware = apiMiddleware;
|