hfs 0.26.9 → 0.29.0
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/README.md +17 -3
- package/admin/assets/index-cbb42a0e.js +415 -0
- package/admin/assets/index-f8049da8.css +1 -0
- package/{frontend/assets/sha512.6af42937.js → admin/assets/sha512-3273321f.js} +2 -2
- package/admin/index.html +2 -2
- package/frontend/assets/index-72e96bb2.js +85 -0
- package/frontend/assets/index-cbcc6ac5.css +1 -0
- package/{admin/assets/sha512.9dfe82e1.js → frontend/assets/sha512-2c2fa926.js} +2 -2
- package/frontend/index.html +3 -3
- package/package.json +8 -10
- package/plugins/vhosting/plugin.js +23 -20
- package/src/QuickZipStream.js +2 -25
- package/src/ThrottledStream.js +1 -1
- package/src/adminApis.js +6 -8
- package/src/api.accounts.js +10 -10
- package/src/api.auth.js +21 -17
- package/src/api.file_list.js +13 -6
- package/src/api.helpers.js +6 -6
- package/src/api.monitor.js +2 -0
- package/src/api.plugins.js +1 -0
- package/src/api.vfs.js +17 -19
- package/src/apiMiddleware.js +16 -9
- package/src/block.js +1 -0
- package/src/commands.js +1 -0
- package/src/config.js +3 -2
- package/src/connections.js +1 -1
- package/src/const.js +19 -7
- package/src/crypt.js +1 -1
- package/src/debounceAsync.js +1 -0
- package/src/events.js +1 -1
- package/src/frontEndApis.js +23 -2
- package/src/github.js +5 -1
- package/src/index.js +5 -3
- package/src/listen.js +6 -3
- package/src/log.js +6 -6
- package/src/middlewares.js +32 -26
- package/src/misc.js +27 -2
- package/src/perm.js +31 -29
- package/src/plugins.js +6 -8
- package/src/serveFile.js +15 -13
- package/src/serveGuiFiles.js +5 -28
- package/src/sse.js +3 -2
- package/src/throttler.js +1 -1
- package/src/update.js +3 -2
- package/src/upload.js +92 -0
- package/src/util-files.js +19 -13
- package/src/util-generators.js +1 -0
- package/src/util-http.js +3 -1
- package/src/util-os.js +41 -0
- package/src/vfs.js +44 -37
- package/src/watchLoad.js +1 -1
- package/src/zip.js +9 -6
- package/admin/assets/index.bb5198ec.js +0 -281
- package/admin/assets/index.dcc78777.css +0 -1
- package/frontend/assets/index.27a78796.js +0 -85
- package/frontend/assets/index.93366732.css +0 -1
package/src/perm.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
// This file is part of HFS - Copyright 2021-
|
|
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
3
|
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.anyAccountCanLoginAdmin = exports.accountCanLoginAdmin = exports.accountCanLogin = exports.accountHasPassword = exports.getFromAccount = exports.delAccount = exports.setAccount = exports.addAccount = exports.renameAccount = exports.normalizeUsername = exports.updateAccount = exports.allowClearTextLogin = exports.saveSrpInfo = exports.getAccount = exports.getCurrentUsernameExpanded = exports.getCurrentUsername =
|
|
7
|
+
exports.anyAccountCanLoginAdmin = exports.accountCanLoginAdmin = exports.accountCanLogin = exports.accountHasPassword = exports.getFromAccount = exports.delAccount = exports.setAccount = exports.addAccount = exports.renameAccount = exports.normalizeUsername = exports.accountsConfig = exports.updateAccount = exports.allowClearTextLogin = exports.saveSrpInfo = exports.getAccount = exports.getCurrentUsernameExpanded = exports.getCurrentUsername = void 0;
|
|
8
8
|
const lodash_1 = __importDefault(require("lodash"));
|
|
9
9
|
const crypt_1 = require("./crypt");
|
|
10
10
|
const misc_1 = require("./misc");
|
|
@@ -12,10 +12,6 @@ const config_1 = require("./config");
|
|
|
12
12
|
const tssrp6a_1 = require("tssrp6a");
|
|
13
13
|
const events_1 = __importDefault(require("./events"));
|
|
14
14
|
let accounts = {};
|
|
15
|
-
function getAccounts() {
|
|
16
|
-
return accounts;
|
|
17
|
-
}
|
|
18
|
-
exports.getAccounts = getAccounts;
|
|
19
15
|
function getCurrentUsername(ctx) {
|
|
20
16
|
var _a;
|
|
21
17
|
return ((_a = ctx.state.account) === null || _a === void 0 ? void 0 : _a.username) || '';
|
|
@@ -63,27 +59,34 @@ async function updateAccount(account, changer) {
|
|
|
63
59
|
console.log('please reset password for account', username);
|
|
64
60
|
process.exit(1);
|
|
65
61
|
}
|
|
66
|
-
if (account.belongs)
|
|
67
|
-
account.belongs = (0, misc_1.wantArray)(account.belongs)
|
|
68
|
-
|
|
62
|
+
if (account.belongs) {
|
|
63
|
+
account.belongs = (0, misc_1.wantArray)(account.belongs);
|
|
64
|
+
lodash_1.default.remove(account.belongs, b => {
|
|
65
|
+
if (b in accounts)
|
|
66
|
+
return;
|
|
67
|
+
console.error(`account ${username} belongs to non-existing ${b}`);
|
|
68
|
+
return true;
|
|
69
|
+
});
|
|
70
|
+
}
|
|
69
71
|
if (was !== JSON.stringify(account))
|
|
70
72
|
saveAccountsAsap();
|
|
71
73
|
}
|
|
72
74
|
exports.updateAccount = updateAccount;
|
|
73
|
-
const saveAccountsAsap = config_1.saveConfigAsap;
|
|
74
|
-
|
|
75
|
-
accountsConfig.sub(
|
|
76
|
-
//
|
|
77
|
-
accounts =
|
|
78
|
-
await Promise.all(lodash_1.default.map(accounts, async (rec, k) => {
|
|
75
|
+
const saveAccountsAsap = () => { (0, config_1.saveConfigAsap)().then(); };
|
|
76
|
+
exports.accountsConfig = (0, config_1.defineConfig)('accounts', {});
|
|
77
|
+
exports.accountsConfig.sub(obj => {
|
|
78
|
+
// consider some validation here
|
|
79
|
+
lodash_1.default.each(accounts = obj, (rec, k) => {
|
|
79
80
|
const norm = normalizeUsername(k);
|
|
80
|
-
if (
|
|
81
|
-
rec
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
81
|
+
if ((rec === null || rec === void 0 ? void 0 : rec.username) !== norm) {
|
|
82
|
+
if (!rec) // an empty object in yaml is parsed as null
|
|
83
|
+
rec = obj[norm] = { username: norm };
|
|
84
|
+
else if ((0, misc_1.objRenameKey)(obj, k, norm))
|
|
85
|
+
saveAccountsAsap();
|
|
86
|
+
(0, misc_1.setHidden)(rec, { username: norm });
|
|
87
|
+
}
|
|
88
|
+
updateAccount(rec).then(); // work password fields
|
|
89
|
+
});
|
|
87
90
|
});
|
|
88
91
|
function normalizeUsername(username) {
|
|
89
92
|
return username.toLocaleLowerCase();
|
|
@@ -120,8 +123,7 @@ function addAccount(username, props) {
|
|
|
120
123
|
return;
|
|
121
124
|
const filteredProps = lodash_1.default.pickBy(lodash_1.default.pick(props, assignableProps), Boolean);
|
|
122
125
|
const copy = (0, misc_1.setHidden)(filteredProps, { username }); // have the field in the object but hidden so that stringification won't include it
|
|
123
|
-
accountsConfig.set(accounts => Object.assign(accounts, { [username]: copy }));
|
|
124
|
-
saveAccountsAsap().then();
|
|
126
|
+
exports.accountsConfig.set(accounts => Object.assign(accounts, { [username]: copy }));
|
|
125
127
|
return copy;
|
|
126
128
|
}
|
|
127
129
|
exports.addAccount = addAccount;
|
|
@@ -136,15 +138,15 @@ function setAccount(username, changes) {
|
|
|
136
138
|
Object.assign(acc, rest);
|
|
137
139
|
if (changes.username)
|
|
138
140
|
renameAccount(username, changes.username);
|
|
139
|
-
saveAccountsAsap()
|
|
141
|
+
saveAccountsAsap();
|
|
140
142
|
return acc;
|
|
141
143
|
}
|
|
142
144
|
exports.setAccount = setAccount;
|
|
143
145
|
function delAccount(username) {
|
|
144
146
|
if (!getAccount(username))
|
|
145
147
|
return false;
|
|
146
|
-
accountsConfig.set(accounts => Object.assign(accounts, { [normalizeUsername(username)]: undefined }));
|
|
147
|
-
saveAccountsAsap()
|
|
148
|
+
exports.accountsConfig.set(accounts => Object.assign(accounts, { [normalizeUsername(username)]: undefined }));
|
|
149
|
+
saveAccountsAsap();
|
|
148
150
|
return true;
|
|
149
151
|
}
|
|
150
152
|
exports.delAccount = delAccount;
|
|
@@ -172,10 +174,10 @@ function accountCanLogin(account) {
|
|
|
172
174
|
}
|
|
173
175
|
exports.accountCanLogin = accountCanLogin;
|
|
174
176
|
function accountCanLoginAdmin(account) {
|
|
175
|
-
return accountCanLogin(account) && getFromAccount(account, a => a.admin);
|
|
177
|
+
return accountCanLogin(account) && Boolean(getFromAccount(account, a => a.admin));
|
|
176
178
|
}
|
|
177
179
|
exports.accountCanLoginAdmin = accountCanLoginAdmin;
|
|
178
180
|
function anyAccountCanLoginAdmin() {
|
|
179
|
-
return
|
|
181
|
+
return Boolean(lodash_1.default.find(exports.accountsConfig.get(), accountCanLoginAdmin));
|
|
180
182
|
}
|
|
181
183
|
exports.anyAccountCanLoginAdmin = anyAccountCanLoginAdmin;
|
package/src/plugins.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
// This file is part of HFS - Copyright 2021-
|
|
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
3
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
4
4
|
if (k2 === undefined) k2 = k;
|
|
5
5
|
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
@@ -92,9 +92,8 @@ function pluginsMiddleware() {
|
|
|
92
92
|
var _a;
|
|
93
93
|
const after = [];
|
|
94
94
|
// run middleware plugins
|
|
95
|
-
for (const id
|
|
95
|
+
for (const [id, pl] of Object.entries(plugins))
|
|
96
96
|
try {
|
|
97
|
-
const pl = plugins[id];
|
|
98
97
|
const res = await ((_a = pl.middleware) === null || _a === void 0 ? void 0 : _a.call(pl, ctx));
|
|
99
98
|
if (res === true)
|
|
100
99
|
ctx.pluginStopped = true;
|
|
@@ -225,7 +224,7 @@ async function rescan() {
|
|
|
225
224
|
try {
|
|
226
225
|
const alreadyRunning = plugins[id];
|
|
227
226
|
console.log(alreadyRunning ? "reloading plugin" : "loading plugin", id);
|
|
228
|
-
const { init, ...data } = await
|
|
227
|
+
const { init, ...data } = await import(module);
|
|
229
228
|
delete data.default;
|
|
230
229
|
deleteModule(require.resolve(module)); // avoid caching at next import
|
|
231
230
|
calculateBadApi(data);
|
|
@@ -277,8 +276,7 @@ async function rescan() {
|
|
|
277
276
|
}
|
|
278
277
|
});
|
|
279
278
|
}
|
|
280
|
-
for (const id
|
|
281
|
-
const p = foundDisabled[id];
|
|
279
|
+
for (const [id, p] of Object.entries(foundDisabled)) {
|
|
282
280
|
const a = availablePlugins[id];
|
|
283
281
|
if ((0, misc_1.same)(a, p))
|
|
284
282
|
continue;
|
|
@@ -293,9 +291,9 @@ async function rescan() {
|
|
|
293
291
|
delete availablePlugins[id];
|
|
294
292
|
events_1.default.emit('pluginUninstalled', id);
|
|
295
293
|
}
|
|
296
|
-
for (const id
|
|
294
|
+
for (const [id, p] of Object.entries(plugins))
|
|
297
295
|
if (!found.includes(id))
|
|
298
|
-
await
|
|
296
|
+
await p.unload();
|
|
299
297
|
}
|
|
300
298
|
exports.rescan = rescan;
|
|
301
299
|
function deleteModule(id) {
|
package/src/serveFile.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
// This file is part of HFS - Copyright 2021-
|
|
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
3
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
4
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
5
|
};
|
|
@@ -27,7 +27,7 @@ function serveFileNode(node) {
|
|
|
27
27
|
const ref = (_a = /\/\/([^:/]+)/.exec(ctx.get('referer'))) === null || _a === void 0 ? void 0 : _a[1]; // extract host from url
|
|
28
28
|
if (ref && ref !== host() // automatic accept if referer is basically the hosting domain
|
|
29
29
|
&& !(0, micromatch_1.isMatch)(ref, allowed))
|
|
30
|
-
return ctx.status = const_1.
|
|
30
|
+
return ctx.status = const_1.HTTP_FORBIDDEN;
|
|
31
31
|
function host() {
|
|
32
32
|
const s = ctx.get('host');
|
|
33
33
|
return s[0] === '[' ? s.slice(1, s.indexOf(']')) : s === null || s === void 0 ? void 0 : s.split(':')[0];
|
|
@@ -50,26 +50,26 @@ function serveFile(source, mime, content) {
|
|
|
50
50
|
if (mime)
|
|
51
51
|
ctx.type = mime;
|
|
52
52
|
if (ctx.method === 'OPTIONS') {
|
|
53
|
-
ctx.status = const_1.
|
|
53
|
+
ctx.status = const_1.HTTP_NO_CONTENT;
|
|
54
54
|
ctx.set({ Allow: 'OPTIONS, GET, HEAD' });
|
|
55
55
|
return;
|
|
56
56
|
}
|
|
57
57
|
if (ctx.method !== 'GET')
|
|
58
|
-
return ctx.status = const_1.
|
|
58
|
+
return ctx.status = const_1.HTTP_METHOD_NOT_ALLOWED;
|
|
59
59
|
try {
|
|
60
60
|
const stats = await (0, util_1.promisify)(fs_1.stat)(source); // using fs's function instead of fs/promises, because only the former is supported by pkg
|
|
61
61
|
ctx.set('Last-Modified', stats.mtime.toUTCString());
|
|
62
62
|
ctx.fileSource = source;
|
|
63
|
-
ctx.status =
|
|
63
|
+
ctx.status = const_1.HTTP_OK;
|
|
64
64
|
if (ctx.fresh)
|
|
65
|
-
return ctx.status =
|
|
65
|
+
return ctx.status = const_1.HTTP_NOT_MODIFIED;
|
|
66
66
|
if (content !== undefined)
|
|
67
67
|
return ctx.body = content;
|
|
68
68
|
const range = getRange(ctx, stats.size);
|
|
69
69
|
ctx.body = (0, fs_1.createReadStream)(source, range);
|
|
70
70
|
}
|
|
71
71
|
catch (_a) {
|
|
72
|
-
return ctx.status =
|
|
72
|
+
return ctx.status = const_1.HTTP_NOT_FOUND;
|
|
73
73
|
}
|
|
74
74
|
};
|
|
75
75
|
}
|
|
@@ -81,23 +81,25 @@ function getRange(ctx, totalSize) {
|
|
|
81
81
|
ctx.response.length = totalSize;
|
|
82
82
|
return;
|
|
83
83
|
}
|
|
84
|
-
const ranges = range.split('=')
|
|
85
|
-
if (
|
|
86
|
-
return ctx.throw(
|
|
84
|
+
const [unit, ranges] = range.split('=');
|
|
85
|
+
if (unit !== 'bytes')
|
|
86
|
+
return ctx.throw(const_1.HTTP_BAD_REQUEST, 'bad range unit');
|
|
87
|
+
if (ranges === null || ranges === void 0 ? void 0 : ranges.includes(','))
|
|
88
|
+
return ctx.throw(const_1.HTTP_BAD_REQUEST, 'multi-range not supported');
|
|
87
89
|
let bytes = ranges === null || ranges === void 0 ? void 0 : ranges.split('-');
|
|
88
90
|
if (!(bytes === null || bytes === void 0 ? void 0 : bytes.length))
|
|
89
|
-
return ctx.throw(
|
|
91
|
+
return ctx.throw(const_1.HTTP_BAD_REQUEST, 'bad range');
|
|
90
92
|
const max = totalSize - 1;
|
|
91
93
|
const start = bytes[0] ? Number(bytes[0]) : Math.max(0, totalSize - Number(bytes[1])); // a negative start is relative to the end
|
|
92
94
|
const end = bytes[0] ? Number(bytes[1] || max) : max;
|
|
93
95
|
// we don't support last-bytes without knowing max
|
|
94
96
|
if (isNaN(end) && isNaN(max) || end > max || start > max) {
|
|
95
|
-
ctx.status =
|
|
97
|
+
ctx.status = const_1.HTTP_RANGE_NOT_SATISFIABLE;
|
|
96
98
|
ctx.set('Content-Range', `bytes ${totalSize}`);
|
|
97
99
|
ctx.body = 'Requested Range Not Satisfiable';
|
|
98
100
|
return;
|
|
99
101
|
}
|
|
100
|
-
ctx.status =
|
|
102
|
+
ctx.status = const_1.HTTP_PARTIAL_CONTENT;
|
|
101
103
|
ctx.set('Content-Range', `bytes ${start}-${isNaN(end) ? '' : end}/${isNaN(totalSize) ? '*' : totalSize}`);
|
|
102
104
|
ctx.response.length = end - start + 1;
|
|
103
105
|
return { start, end };
|
package/src/serveGuiFiles.js
CHANGED
|
@@ -1,28 +1,5 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
// This file is part of HFS - Copyright 2021-
|
|
3
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
4
|
-
if (k2 === undefined) k2 = k;
|
|
5
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
6
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
7
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
8
|
-
}
|
|
9
|
-
Object.defineProperty(o, k2, desc);
|
|
10
|
-
}) : (function(o, m, k, k2) {
|
|
11
|
-
if (k2 === undefined) k2 = k;
|
|
12
|
-
o[k2] = m[k];
|
|
13
|
-
}));
|
|
14
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
15
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
16
|
-
}) : function(o, v) {
|
|
17
|
-
o["default"] = v;
|
|
18
|
-
});
|
|
19
|
-
var __importStar = (this && this.__importStar) || function (mod) {
|
|
20
|
-
if (mod && mod.__esModule) return mod;
|
|
21
|
-
var result = {};
|
|
22
|
-
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
23
|
-
__setModuleDefault(result, mod);
|
|
24
|
-
return result;
|
|
25
|
-
};
|
|
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
|
|
26
3
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
27
4
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
28
5
|
};
|
|
@@ -43,12 +20,12 @@ function serveStatic(uri) {
|
|
|
43
20
|
const cache = {};
|
|
44
21
|
return async (ctx, next) => {
|
|
45
22
|
if (ctx.method === 'OPTIONS') {
|
|
46
|
-
ctx.status = const_1.
|
|
23
|
+
ctx.status = const_1.HTTP_NO_CONTENT;
|
|
47
24
|
ctx.set({ Allow: 'OPTIONS, GET' });
|
|
48
25
|
return;
|
|
49
26
|
}
|
|
50
27
|
if (ctx.method !== 'GET')
|
|
51
|
-
return ctx.status = const_1.
|
|
28
|
+
return ctx.status = const_1.HTTP_METHOD_NOT_ALLOWED;
|
|
52
29
|
const serveApp = shouldServeApp(ctx);
|
|
53
30
|
const fullPath = (0, path_1.join)(__dirname, '..', DEV_STATIC, folder, serveApp ? '/index.html' : ctx.path);
|
|
54
31
|
const content = await (0, misc_1.getOrSet)(cache, ctx.path, async () => {
|
|
@@ -57,7 +34,7 @@ function serveStatic(uri) {
|
|
|
57
34
|
: adjustBundlerLinks(ctx.path, uri, data);
|
|
58
35
|
});
|
|
59
36
|
if (content === null)
|
|
60
|
-
return ctx.status =
|
|
37
|
+
return ctx.status = const_1.HTTP_NOT_FOUND;
|
|
61
38
|
if (!serveApp)
|
|
62
39
|
return (0, serveFile_1.serveFile)(fullPath, 'auto', content)(ctx, next);
|
|
63
40
|
// we don't cache the index as it's small and may prevent plugins change to apply
|
|
@@ -89,7 +66,7 @@ function serveProxied(port, uri) {
|
|
|
89
66
|
return;
|
|
90
67
|
console.debug('proxied on port', port);
|
|
91
68
|
let proxy;
|
|
92
|
-
|
|
69
|
+
import('koa-better-http-proxy').then(lib => // dynamic import to avoid having this in final distribution
|
|
93
70
|
proxy = lib.default('127.0.0.1:' + port, {
|
|
94
71
|
proxyReqPathResolver: (ctx) => shouldServeApp(ctx) ? '/' : ctx.path,
|
|
95
72
|
userResDecorator(res, data, ctx) {
|
package/src/sse.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
// This file is part of HFS - Copyright 2021-
|
|
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
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
4
|
const stream_1 = require("stream");
|
|
5
|
+
const const_1 = require("./const");
|
|
5
6
|
function createSSE(ctx) {
|
|
6
7
|
const { socket } = ctx.req;
|
|
7
8
|
socket.setTimeout(0);
|
|
@@ -13,7 +14,7 @@ function createSSE(ctx) {
|
|
|
13
14
|
'Connection': 'keep-alive',
|
|
14
15
|
'X-Accel-Buffering': 'no', // avoid buffering when reverse-proxied through nginx
|
|
15
16
|
});
|
|
16
|
-
ctx.status =
|
|
17
|
+
ctx.status = const_1.HTTP_OK;
|
|
17
18
|
return ctx.body = new stream_1.Transform({
|
|
18
19
|
objectMode: true,
|
|
19
20
|
transform(chunk, encoding, cb) {
|
package/src/throttler.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
// This file is part of HFS - Copyright 2021-
|
|
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
3
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
4
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
5
|
};
|
package/src/update.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
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
|
|
2
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
4
|
exports.update = exports.getUpdate = void 0;
|
|
4
5
|
const github_1 = require("./github");
|
|
@@ -29,7 +30,7 @@ async function update() {
|
|
|
29
30
|
throw "asset not found";
|
|
30
31
|
const url = asset.browser_download_url;
|
|
31
32
|
console.log("downloading", url);
|
|
32
|
-
const bin = process.
|
|
33
|
+
const bin = process.argv0;
|
|
33
34
|
const binPath = (0, path_1.dirname)(bin);
|
|
34
35
|
const binFile = (0, path_1.basename)(bin);
|
|
35
36
|
const newBinFile = 'new-' + binFile;
|
|
@@ -63,7 +64,7 @@ async function update() {
|
|
|
63
64
|
}
|
|
64
65
|
exports.update = update;
|
|
65
66
|
if (const_1.argv.updating) { // we were launched with a temporary name, restore original name to avoid breaking references
|
|
66
|
-
const bin = process.
|
|
67
|
+
const bin = process.argv0;
|
|
67
68
|
(0, fs_1.renameSync)(bin, (0, path_1.join)((0, path_1.dirname)(bin), const_1.argv.updating));
|
|
68
69
|
console.log("renamed binary file to", const_1.argv.updating);
|
|
69
70
|
}
|
package/src/upload.js
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.uploadWriter = exports.minAvailableMb = exports.deleteUnfinishedUploadsAfter = void 0;
|
|
7
|
+
const vfs_1 = require("./vfs");
|
|
8
|
+
const const_1 = require("./const");
|
|
9
|
+
const path_1 = require("path");
|
|
10
|
+
const fs_1 = __importDefault(require("fs"));
|
|
11
|
+
const misc_1 = require("./misc");
|
|
12
|
+
const frontEndApis_1 = require("./frontEndApis");
|
|
13
|
+
const config_1 = require("./config");
|
|
14
|
+
const util_os_1 = require("./util-os");
|
|
15
|
+
exports.deleteUnfinishedUploadsAfter = (0, config_1.defineConfig)('delete_unfinished_uploads_after');
|
|
16
|
+
exports.minAvailableMb = (0, config_1.defineConfig)('min_available_mb', 100);
|
|
17
|
+
const waitingToBeDeleted = {};
|
|
18
|
+
function uploadWriter(base, path, ctx) {
|
|
19
|
+
if (!base.source || !(0, vfs_1.hasPermission)(base, 'can_upload', ctx))
|
|
20
|
+
return fail(base.can_upload === false ? const_1.HTTP_FORBIDDEN : const_1.HTTP_UNAUTHORIZED);
|
|
21
|
+
const fullPath = (0, path_1.join)(base.source, path);
|
|
22
|
+
const dir = (0, path_1.dirname)(fullPath);
|
|
23
|
+
const min = exports.minAvailableMb.get() * (1 << 20);
|
|
24
|
+
const reqSize = Number(ctx.headers["content-length"]);
|
|
25
|
+
if (min && reqSize)
|
|
26
|
+
try {
|
|
27
|
+
if (reqSize > (0, util_os_1.getFreeDiskSync)(dir) - min)
|
|
28
|
+
return fail(const_1.HTTP_PAYLOAD_TOO_LARGE);
|
|
29
|
+
}
|
|
30
|
+
catch (e) {
|
|
31
|
+
console.warn("can't check disk size", String(e));
|
|
32
|
+
}
|
|
33
|
+
fs_1.default.mkdirSync(dir, { recursive: true });
|
|
34
|
+
const keepName = (0, path_1.basename)(fullPath).slice(-200);
|
|
35
|
+
let tempName = (0, path_1.join)(dir, 'hfs$upload-' + keepName);
|
|
36
|
+
const resumable = fs_1.default.existsSync(tempName) && tempName;
|
|
37
|
+
if (resumable)
|
|
38
|
+
tempName = (0, path_1.join)(dir, 'hfs$upload2-' + keepName);
|
|
39
|
+
const resume = Number(ctx.query.resume);
|
|
40
|
+
const size = resumable && (0, misc_1.try_)(() => fs_1.default.statSync(resumable).size);
|
|
41
|
+
if (size === undefined) // stat failed
|
|
42
|
+
return fail(const_1.HTTP_SERVER_ERROR);
|
|
43
|
+
if (resume > size)
|
|
44
|
+
return fail(const_1.HTTP_RANGE_NOT_SATISFIABLE);
|
|
45
|
+
if (!resume && resumable) {
|
|
46
|
+
const timeout = 30;
|
|
47
|
+
(0, frontEndApis_1.notifyClient)(ctx, 'upload.resumable', { [path]: size, expires: Date.now() + timeout * 1000 });
|
|
48
|
+
delayedDelete(resumable, timeout, () => fs_1.default.rename(tempName, resumable, err => {
|
|
49
|
+
if (!err)
|
|
50
|
+
tempName = resumable;
|
|
51
|
+
}));
|
|
52
|
+
}
|
|
53
|
+
const resuming = resume && resumable;
|
|
54
|
+
const ret = resuming ? fs_1.default.createWriteStream(resumable, { flags: 'r+', start: resume })
|
|
55
|
+
: fs_1.default.createWriteStream(tempName);
|
|
56
|
+
if (resuming) {
|
|
57
|
+
fs_1.default.rm(tempName, () => { });
|
|
58
|
+
tempName = resumable;
|
|
59
|
+
}
|
|
60
|
+
cancelDeletion(tempName);
|
|
61
|
+
ret.on('close', () => {
|
|
62
|
+
if (!ctx.req.aborted)
|
|
63
|
+
return fs_1.default.rename(tempName, fullPath, err => {
|
|
64
|
+
err && console.error("couldn't rename temp to", fullPath, String(err));
|
|
65
|
+
if (resumable)
|
|
66
|
+
delayedDelete(resumable, 0);
|
|
67
|
+
});
|
|
68
|
+
if (resumable) // we don't want to be left with 2 temp files
|
|
69
|
+
return delayedDelete(tempName, 0);
|
|
70
|
+
const sec = exports.deleteUnfinishedUploadsAfter.get();
|
|
71
|
+
if (typeof sec !== 'number')
|
|
72
|
+
return;
|
|
73
|
+
delayedDelete(tempName, sec);
|
|
74
|
+
});
|
|
75
|
+
return ret;
|
|
76
|
+
function delayedDelete(path, secs, cb) {
|
|
77
|
+
clearTimeout(waitingToBeDeleted[path]);
|
|
78
|
+
waitingToBeDeleted[path] = setTimeout(() => {
|
|
79
|
+
delete waitingToBeDeleted[path];
|
|
80
|
+
fs_1.default.rm(path, () => cb === null || cb === void 0 ? void 0 : cb());
|
|
81
|
+
}, secs * 1000);
|
|
82
|
+
}
|
|
83
|
+
function cancelDeletion(path) {
|
|
84
|
+
clearTimeout(waitingToBeDeleted[path]);
|
|
85
|
+
delete waitingToBeDeleted[path];
|
|
86
|
+
}
|
|
87
|
+
function fail(status) {
|
|
88
|
+
ctx.status = status;
|
|
89
|
+
(0, frontEndApis_1.notifyClient)(ctx, 'upload.status', { [path]: ctx.status }); // allow browsers to detect failure while still sending body
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
exports.uploadWriter = uploadWriter;
|
package/src/util-files.js
CHANGED
|
@@ -1,16 +1,17 @@
|
|
|
1
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
|
|
2
3
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
4
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
5
|
};
|
|
5
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.
|
|
7
|
+
exports.prepareFolder = exports.unzip = exports.dirStream = exports.adjustStaticPathForGlob = exports.isWindowsDrive = exports.dirTraversal = exports.watchDir = exports.readFileBusy = exports.isFile = exports.isDirectory = void 0;
|
|
7
8
|
const promises_1 = __importDefault(require("fs/promises"));
|
|
8
9
|
const misc_1 = require("./misc");
|
|
9
10
|
const fs_1 = require("fs");
|
|
10
11
|
const path_1 = require("path");
|
|
11
12
|
const fast_glob_1 = __importDefault(require("fast-glob"));
|
|
12
13
|
const const_1 = require("./const");
|
|
13
|
-
const
|
|
14
|
+
const util_os_1 = require("./util-os");
|
|
14
15
|
const stream_1 = require("stream");
|
|
15
16
|
// @ts-ignore
|
|
16
17
|
const unzip_stream_1 = __importDefault(require("unzip-stream"));
|
|
@@ -114,21 +115,12 @@ async function* dirStream(path, deep) {
|
|
|
114
115
|
async function getItemsToSkip(path) {
|
|
115
116
|
if (!const_1.IS_WINDOWS)
|
|
116
117
|
return;
|
|
117
|
-
const out = await
|
|
118
|
+
const out = await (0, util_os_1.runCmd)('dir', ['/ah', '/b', path.replace(/\//g, '\\')])
|
|
118
119
|
.catch(() => ''); // error in case of no matching file
|
|
119
120
|
return out.split('\r\n').slice(0, -1);
|
|
120
121
|
}
|
|
121
122
|
}
|
|
122
123
|
exports.dirStream = dirStream;
|
|
123
|
-
function run(cmd, args = []) {
|
|
124
|
-
return new Promise((resolve, reject) => (0, child_process_1.execFile)('cmd', ['/c', cmd, ...args], (err, stdout) => {
|
|
125
|
-
if (err)
|
|
126
|
-
reject(err);
|
|
127
|
-
else
|
|
128
|
-
resolve(stdout);
|
|
129
|
-
}));
|
|
130
|
-
}
|
|
131
|
-
exports.run = run;
|
|
132
124
|
async function unzip(stream, cb) {
|
|
133
125
|
let pending = Promise.resolve();
|
|
134
126
|
return new Promise(resolve => stream.pipe(unzip_stream_1.default.Parse())
|
|
@@ -140,9 +132,23 @@ async function unzip(stream, cb) {
|
|
|
140
132
|
return entry.autodrain();
|
|
141
133
|
await pending; // don't overlap writings
|
|
142
134
|
console.debug('unzip', dest);
|
|
143
|
-
|
|
135
|
+
await prepareFolder(dest);
|
|
144
136
|
const thisFile = entry.pipe((0, fs_1.createWriteStream)(dest));
|
|
145
137
|
pending = (0, stream_1.once)(thisFile, 'finish');
|
|
146
138
|
}));
|
|
147
139
|
}
|
|
148
140
|
exports.unzip = unzip;
|
|
141
|
+
async function prepareFolder(path, dirnameIt = true) {
|
|
142
|
+
if (dirnameIt)
|
|
143
|
+
path = (0, path_1.dirname)(path);
|
|
144
|
+
if (isWindowsDrive(path))
|
|
145
|
+
return;
|
|
146
|
+
try {
|
|
147
|
+
await promises_1.default.mkdir(path, { recursive: true });
|
|
148
|
+
return true;
|
|
149
|
+
}
|
|
150
|
+
catch (_a) {
|
|
151
|
+
return false;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
exports.prepareFolder = prepareFolder;
|
package/src/util-generators.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
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
|
|
2
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
4
|
exports.asyncGeneratorToReadable = exports.asyncGeneratorToArray = exports.filterMapGenerator = void 0;
|
|
4
5
|
// callback can return undefined to skip element
|
package/src/util-http.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
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
|
|
2
3
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
4
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
5
|
};
|
|
5
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
7
|
exports.httpsStream = exports.httpsString = void 0;
|
|
7
8
|
const node_https_1 = __importDefault(require("node:https"));
|
|
9
|
+
const const_1 = require("./const");
|
|
8
10
|
function httpsString(url, options = {}) {
|
|
9
11
|
return httpsStream(url, options).then(res => new Promise(resolve => {
|
|
10
12
|
let buf = '';
|
|
@@ -21,7 +23,7 @@ function httpsStream(url, options = {}) {
|
|
|
21
23
|
node_https_1.default.request(url, options, res => {
|
|
22
24
|
if (!res.statusCode || res.statusCode >= 400)
|
|
23
25
|
throw res;
|
|
24
|
-
if (res.statusCode ===
|
|
26
|
+
if (res.statusCode === const_1.HTTP_TEMPORARY_REDIRECT && res.headers.location)
|
|
25
27
|
return resolve(httpsStream(res.headers.location, options));
|
|
26
28
|
resolve(res);
|
|
27
29
|
}).on('error', reject).end();
|
package/src/util-os.js
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.runCmd = exports.getDrives = exports.getFreeDiskSync = void 0;
|
|
4
|
+
const path_1 = require("path");
|
|
5
|
+
const child_process_1 = require("child_process");
|
|
6
|
+
const misc_1 = require("./misc");
|
|
7
|
+
const util_1 = require("util");
|
|
8
|
+
const const_1 = require("./const");
|
|
9
|
+
function getFreeDiskSync(path) {
|
|
10
|
+
var _a;
|
|
11
|
+
if (const_1.IS_WINDOWS) {
|
|
12
|
+
const drive = (0, path_1.resolve)(path).slice(0, 2).toUpperCase();
|
|
13
|
+
const out = (0, child_process_1.execSync)('wmic logicaldisk get FreeSpace,name /format:list').toString().replace(/\r/g, '');
|
|
14
|
+
const one = out.split(/\n\n+/).find(x => x.includes('Name=' + drive));
|
|
15
|
+
if (!one)
|
|
16
|
+
throw Error('miss');
|
|
17
|
+
return Number((_a = /FreeSpace=(\d+)/.exec(one)) === null || _a === void 0 ? void 0 : _a[1]);
|
|
18
|
+
}
|
|
19
|
+
const out = (0, misc_1.try_)(() => (0, child_process_1.execSync)(`df -k ${path}`).toString(), err => {
|
|
20
|
+
throw err.status === 1 ? Error('miss')
|
|
21
|
+
: err.status === 127 ? Error('unsupported')
|
|
22
|
+
: err;
|
|
23
|
+
});
|
|
24
|
+
if (!(out === null || out === void 0 ? void 0 : out.startsWith('Filesystem')))
|
|
25
|
+
throw Error('unsupported');
|
|
26
|
+
const one = out.split('\n')[1];
|
|
27
|
+
const free = Number(one.split(/\s+/)[3]);
|
|
28
|
+
return free * 1024;
|
|
29
|
+
}
|
|
30
|
+
exports.getFreeDiskSync = getFreeDiskSync;
|
|
31
|
+
async function getDrives() {
|
|
32
|
+
const { stdout } = await (0, util_1.promisify)(child_process_1.exec)('wmic logicaldisk get name');
|
|
33
|
+
return stdout.split('\n').slice(1).map(x => x.trim()).filter(Boolean);
|
|
34
|
+
}
|
|
35
|
+
exports.getDrives = getDrives;
|
|
36
|
+
// execute win32 shell commands
|
|
37
|
+
async function runCmd(cmd, args = []) {
|
|
38
|
+
const { stdout, stderr } = await (0, util_1.promisify)(child_process_1.execFile)('cmd', ['/c', cmd, ...args]);
|
|
39
|
+
return stderr || stdout;
|
|
40
|
+
}
|
|
41
|
+
exports.runCmd = runCmd;
|