hfs 0.56.0 → 0.57.0-beta2
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-DlA4sSmp.js → index-Cl7PJ7_4.js} +263 -263
- package/admin/assets/{sha512--f9T-TYW.js → sha512-DwXzavpM.js} +1 -1
- package/admin/index.html +14 -2
- package/frontend/assets/index-legacy-0ZDBWQI9.js +50 -0
- package/frontend/assets/{sha512-legacy-CX3vTVvC.js → sha512-legacy-slBG5k4n.js} +1 -1
- package/frontend/index.html +1 -1
- package/package.json +2 -2
- package/plugins/antibrute/plugin.js +25 -27
- package/src/SendList.js +2 -2
- package/src/acme.js +1 -2
- package/src/adminApis.js +1 -0
- package/src/api.accounts.js +3 -0
- package/src/api.auth.js +27 -4
- package/src/api.get_file_list.js +2 -6
- package/src/api.log.js +9 -4
- package/src/api.monitor.js +1 -1
- package/src/api.plugins.js +13 -0
- package/src/apiMiddleware.js +1 -1
- package/src/auth.js +17 -1
- package/src/comments.js +31 -8
- package/src/config.js +2 -0
- package/src/connections.js +16 -5
- package/src/const.js +4 -4
- package/src/cross-const.js +2 -1
- package/src/cross.js +13 -3
- package/src/customHtml.js +4 -2
- package/src/events.js +7 -5
- package/src/fileAttr.js +10 -5
- package/src/frontEndApis.js +0 -12
- package/src/geo.js +1 -0
- package/src/langs/hfs-lang-ar.json +4 -1
- package/src/langs/hfs-lang-de.json +4 -1
- package/src/langs/hfs-lang-el.json +4 -1
- package/src/langs/hfs-lang-es.json +4 -1
- package/src/langs/hfs-lang-fi.json +4 -1
- package/src/langs/hfs-lang-fr.json +4 -1
- package/src/langs/hfs-lang-hu.json +4 -1
- package/src/langs/hfs-lang-it.json +4 -1
- package/src/langs/hfs-lang-ja.json +4 -1
- package/src/langs/hfs-lang-ko.json +4 -1
- package/src/langs/hfs-lang-ms.json +4 -1
- package/src/langs/hfs-lang-nl.json +4 -1
- package/src/langs/hfs-lang-pt-br.json +4 -1
- package/src/langs/hfs-lang-ro.json +4 -1
- package/src/langs/hfs-lang-ru.json +16 -13
- package/src/langs/hfs-lang-sr.json +4 -1
- package/src/langs/hfs-lang-th.json +4 -1
- package/src/langs/hfs-lang-tr.json +7 -1
- package/src/langs/hfs-lang-uk.json +4 -1
- package/src/langs/hfs-lang-vi.json +8 -5
- package/src/langs/hfs-lang-zh-tw.json +4 -1
- package/src/langs/hfs-lang-zh.json +4 -1
- package/src/listen.js +3 -2
- package/src/log.js +10 -3
- package/src/middlewares.js +7 -23
- package/src/misc.js +6 -35
- package/src/perm.js +5 -3
- package/src/plugins.js +44 -4
- package/src/roots.js +1 -3
- package/src/serveGuiAndSharedFiles.js +18 -10
- package/src/serveGuiFiles.js +2 -4
- package/src/throttler.js +3 -2
- package/src/update.js +1 -1
- package/src/upload.js +1 -1
- package/src/vfs.js +1 -1
- package/README.md +0 -177
- package/frontend/assets/index-legacy-3T51dA5w.js +0 -50
package/src/log.js
CHANGED
|
@@ -88,7 +88,7 @@ const logUA = (0, config_1.defineConfig)(misc_1.CFG.log_ua, false);
|
|
|
88
88
|
const logSpam = (0, config_1.defineConfig)(misc_1.CFG.log_spam, false);
|
|
89
89
|
const debounce = lodash_1.default.debounce(cb => cb(), 1000); // with this technique, i'll be able to debounce some code respecting the references in its closure
|
|
90
90
|
const logMw = async (ctx, next) => {
|
|
91
|
-
const now = new Date();
|
|
91
|
+
const now = new Date(); // request start
|
|
92
92
|
// do it now so it's available for returning plugins
|
|
93
93
|
ctx.state.completed = Promise.race([(0, stream_1.once)(ctx.res, 'finish'), (0, stream_1.once)(ctx.res, 'close')]);
|
|
94
94
|
await next();
|
|
@@ -140,8 +140,15 @@ const logMw = async (ctx, next) => {
|
|
|
140
140
|
const user = (0, auth_1.getCurrentUsername)(ctx);
|
|
141
141
|
const length = (_c = ctx.state.length) !== null && _c !== void 0 ? _c : ctx.length;
|
|
142
142
|
const uri = ctx.originalUrl;
|
|
143
|
-
|
|
144
|
-
|
|
143
|
+
const duration = (Date.now() - Number(now)) / 1000;
|
|
144
|
+
ctx.logExtra(ctx.vfsNode && {
|
|
145
|
+
speed: Math.round(length / duration),
|
|
146
|
+
...ctx.state.includesLastByte && ctx.res.finished && { dl: 1 }
|
|
147
|
+
} || ctx.state.uploadPath && {
|
|
148
|
+
ul: ctx.state.uploads,
|
|
149
|
+
size: ctx.state.opTotal,
|
|
150
|
+
speed: Math.round((ctx.state.opTotal - (ctx.state.opOffset || 0)) / duration)
|
|
151
|
+
});
|
|
145
152
|
if (conn === null || conn === void 0 ? void 0 : conn.country)
|
|
146
153
|
ctx.logExtra({ country: conn.country });
|
|
147
154
|
if (logUA.get())
|
package/src/middlewares.js
CHANGED
|
@@ -111,42 +111,26 @@ const prepareState = async (ctx, next) => {
|
|
|
111
111
|
ctx.state.revProxyPath = ctx.get('x-forwarded-prefix');
|
|
112
112
|
(0, connections_1.updateConnectionForCtx)(ctx);
|
|
113
113
|
await next();
|
|
114
|
-
|
|
114
|
+
function urlLogin() {
|
|
115
115
|
const { login } = ctx.query;
|
|
116
116
|
if (!login)
|
|
117
117
|
return;
|
|
118
118
|
const [u, p] = (0, misc_1.splitAt)(':', String(login));
|
|
119
|
-
const a = await doLogin(u, p, 'url');
|
|
120
|
-
if (!a)
|
|
121
|
-
return;
|
|
122
119
|
ctx.redirect(ctx.originalUrl.slice(0, -ctx.querystring.length - 1)); // redirect to hide credentials
|
|
123
|
-
return
|
|
120
|
+
return u && (0, auth_1.clearTextLogin)(ctx, u, p, 'url');
|
|
124
121
|
}
|
|
125
122
|
function getHttpAccount() {
|
|
126
|
-
var _a;
|
|
123
|
+
var _a, _b;
|
|
127
124
|
const b64 = allowAuthorizationHeader.get() && ((_a = ctx.get('authorization')) === null || _a === void 0 ? void 0 : _a.split(' ')[1]);
|
|
128
125
|
if (!b64)
|
|
129
126
|
return;
|
|
130
127
|
try {
|
|
131
128
|
const [u, p] = atob(b64).split(':');
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
}
|
|
136
|
-
async function doLogin(u, p, via) {
|
|
137
|
-
var _a, _b;
|
|
138
|
-
if (!u || u === ((_a = ctx.session) === null || _a === void 0 ? void 0 : _a.username))
|
|
139
|
-
return; // providing credentials, but not needed
|
|
140
|
-
if ((_b = (await events_1.default.emitAsync('attemptingLogin', { ctx, username: u, via }))) === null || _b === void 0 ? void 0 : _b.isDefaultPrevented())
|
|
141
|
-
return;
|
|
142
|
-
const a = await (0, auth_1.srpCheck)(u, p);
|
|
143
|
-
if (a) {
|
|
144
|
-
await (0, auth_1.setLoggedIn)(ctx, a.username);
|
|
145
|
-
ctx.headers['x-username'] = a.username; // give an easier way to determine if the login was successful
|
|
129
|
+
if (!u || u === ((_b = ctx.session) === null || _b === void 0 ? void 0 : _b.username))
|
|
130
|
+
return; // providing credentials, but not needed
|
|
131
|
+
return (0, auth_1.clearTextLogin)(ctx, u, p || '', 'header');
|
|
146
132
|
}
|
|
147
|
-
|
|
148
|
-
events_1.default.emit('failedLogin', ctx, { username: u, via });
|
|
149
|
-
return a;
|
|
133
|
+
catch (_c) { }
|
|
150
134
|
}
|
|
151
135
|
};
|
|
152
136
|
exports.prepareState = prepareState;
|
package/src/misc.js
CHANGED
|
@@ -18,9 +18,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
18
18
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
19
19
|
};
|
|
20
20
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
21
|
-
exports.
|
|
21
|
+
exports.createStreamLimiter = exports.apiAssertTypes = exports.AsapStream = exports.asyncGeneratorToReadable = exports.same = exports.makeNetMatcher = exports.netMatches = exports.isLocalHost = exports.pattern2filter = void 0;
|
|
22
22
|
const path_1 = require("path");
|
|
23
|
-
const assert_1 = __importDefault(require("assert"));
|
|
24
23
|
__exportStar(require("./util-http"), exports);
|
|
25
24
|
__exportStar(require("./util-files"), exports);
|
|
26
25
|
__exportStar(require("./fileAttr"), exports);
|
|
@@ -32,10 +31,6 @@ const apiMiddleware_1 = require("./apiMiddleware");
|
|
|
32
31
|
const const_1 = require("./const");
|
|
33
32
|
const cross_1 = require("./cross");
|
|
34
33
|
const net_1 = require("net");
|
|
35
|
-
const vfs_1 = require("./vfs");
|
|
36
|
-
const events_1 = __importDefault(require("./events"));
|
|
37
|
-
const promises_1 = require("fs/promises");
|
|
38
|
-
const comments_1 = require("./comments");
|
|
39
34
|
const lodash_1 = __importDefault(require("lodash"));
|
|
40
35
|
function pattern2filter(pattern) {
|
|
41
36
|
const matcher = (0, cross_1.makeMatcher)(pattern.includes('*') ? pattern // if you specify *, we'll respect its position
|
|
@@ -48,9 +43,11 @@ function isLocalHost(c) {
|
|
|
48
43
|
return ip && (0, cross_1.isIpLocalHost)(ip);
|
|
49
44
|
}
|
|
50
45
|
exports.isLocalHost = isLocalHost;
|
|
51
|
-
// this will memory-leak over mask, so be careful with what you use this
|
|
46
|
+
// this will memory-leak over mask, so be careful with what you use this. Object is 3x faster than _.memoize
|
|
52
47
|
function netMatches(ip, mask, emptyMaskReturns = false) {
|
|
53
|
-
|
|
48
|
+
var _a, _b;
|
|
49
|
+
const cache = (_a = netMatches).cache || (_a.cache = {});
|
|
50
|
+
return (cache[_b = mask + (emptyMaskReturns ? '1' : '0')] || (cache[_b] = makeNetMatcher(mask, emptyMaskReturns)))(ip); // cache the matcher
|
|
54
51
|
}
|
|
55
52
|
exports.netMatches = netMatches;
|
|
56
53
|
function makeNetMatcher(mask, emptyMaskReturns = false) {
|
|
@@ -108,13 +105,7 @@ function parseAddress(s) {
|
|
|
108
105
|
return new node_net_1.SocketAddress({ address: s, family: (0, net_1.isIPv6)(s) ? 'ipv6' : 'ipv4' });
|
|
109
106
|
}
|
|
110
107
|
function same(a, b) {
|
|
111
|
-
|
|
112
|
-
assert_1.default.deepStrictEqual(a, b);
|
|
113
|
-
return true;
|
|
114
|
-
}
|
|
115
|
-
catch (_a) {
|
|
116
|
-
return false;
|
|
117
|
-
}
|
|
108
|
+
return lodash_1.default.isEqual(a, b);
|
|
118
109
|
}
|
|
119
110
|
exports.same = same;
|
|
120
111
|
function asyncGeneratorToReadable(generator) {
|
|
@@ -175,23 +166,3 @@ function createStreamLimiter(limit) {
|
|
|
175
166
|
});
|
|
176
167
|
}
|
|
177
168
|
exports.createStreamLimiter = createStreamLimiter;
|
|
178
|
-
async function deleteNode(ctx, node, uri) {
|
|
179
|
-
var _a;
|
|
180
|
-
const { source } = node;
|
|
181
|
-
if (!source)
|
|
182
|
-
return const_1.HTTP_METHOD_NOT_ALLOWED;
|
|
183
|
-
if ((0, vfs_1.statusCodeForMissingPerm)(node, 'can_delete', ctx))
|
|
184
|
-
return ctx.status;
|
|
185
|
-
try {
|
|
186
|
-
if ((_a = (await events_1.default.emitAsync('deleting', { node, ctx }))) === null || _a === void 0 ? void 0 : _a.isDefaultPrevented())
|
|
187
|
-
return null; // stop
|
|
188
|
-
ctx.logExtra(null, { target: decodeURI(uri) });
|
|
189
|
-
await (0, promises_1.rm)(source, { recursive: true });
|
|
190
|
-
void (0, comments_1.setCommentFor)(source, '');
|
|
191
|
-
return true;
|
|
192
|
-
}
|
|
193
|
-
catch (e) {
|
|
194
|
-
return e;
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
exports.deleteNode = deleteNode;
|
package/src/perm.js
CHANGED
|
@@ -30,7 +30,8 @@ function expandUsername(who) {
|
|
|
30
30
|
exports.expandUsername = expandUsername;
|
|
31
31
|
// check if current username or any ancestor match the provided usernames
|
|
32
32
|
function ctxBelongsTo(ctx, usernames) {
|
|
33
|
-
|
|
33
|
+
var _a;
|
|
34
|
+
return ((_a = ctx.state).usernames || (_a.usernames = expandUsername((0, auth_1.getCurrentUsername)(ctx)))) // cache ancestors' usernames inside context state
|
|
34
35
|
.some((u) => usernames.includes(u));
|
|
35
36
|
}
|
|
36
37
|
exports.ctxBelongsTo = ctxBelongsTo;
|
|
@@ -181,7 +182,8 @@ function accountHasPassword(account) {
|
|
|
181
182
|
}
|
|
182
183
|
exports.accountHasPassword = accountHasPassword;
|
|
183
184
|
function accountCanLogin(account) {
|
|
184
|
-
|
|
185
|
+
var _a;
|
|
186
|
+
return (accountHasPassword(account) || ((_a = account.plugin) === null || _a === void 0 ? void 0 : _a.auth)) && !allDisabled(account);
|
|
185
187
|
}
|
|
186
188
|
exports.accountCanLogin = accountCanLogin;
|
|
187
189
|
function allDisabled(account) {
|
|
@@ -193,7 +195,7 @@ function allDisabled(account) {
|
|
|
193
195
|
&& account.belongs.map(u => getAccount(u, false)).every(a => a && allDisabled(a)));
|
|
194
196
|
}
|
|
195
197
|
function accountCanLoginAdmin(account) {
|
|
196
|
-
return accountCanLogin(account) &&
|
|
198
|
+
return accountCanLogin(account) && getFromAccount(account, a => a.admin) || false;
|
|
197
199
|
}
|
|
198
200
|
exports.accountCanLoginAdmin = accountCanLoginAdmin;
|
|
199
201
|
async function changeSrpHelper(account, salt, verifier) {
|
package/src/plugins.js
CHANGED
|
@@ -133,14 +133,33 @@ function getPluginConfigFields(id) {
|
|
|
133
133
|
exports.getPluginConfigFields = getPluginConfigFields;
|
|
134
134
|
async function initPlugin(pl, morePassedToInit) {
|
|
135
135
|
var _a;
|
|
136
|
+
const undoEvents = [];
|
|
137
|
+
const timeouts = [];
|
|
136
138
|
const res = await ((_a = pl.init) === null || _a === void 0 ? void 0 : _a.call(pl, {
|
|
137
139
|
Const,
|
|
138
140
|
require,
|
|
139
141
|
getConnections: connections_1.getConnections,
|
|
140
|
-
|
|
142
|
+
// intercept all subscriptions, so to be able to undo them on unload
|
|
143
|
+
events: Object.create(events_1.default, (0, misc_1.objFromKeys)(['on', 'once', 'multi'], k => ({
|
|
144
|
+
value() {
|
|
145
|
+
const ret = events_1.default[k](...arguments);
|
|
146
|
+
undoEvents.push(ret);
|
|
147
|
+
return ret;
|
|
148
|
+
}
|
|
149
|
+
}))),
|
|
141
150
|
log: console.log,
|
|
142
151
|
setError(msg) { setError((morePassedToInit === null || morePassedToInit === void 0 ? void 0 : morePassedToInit.id) || 'server_code', msg); },
|
|
143
152
|
getHfsConfig: config_1.getConfig,
|
|
153
|
+
setInterval() {
|
|
154
|
+
const ret = setInterval(...arguments);
|
|
155
|
+
timeouts.push(ret); // intervals can be canceled by clearTimeout (source: MDN)
|
|
156
|
+
return ret;
|
|
157
|
+
},
|
|
158
|
+
setTimeout() {
|
|
159
|
+
const ret = setTimeout(...arguments);
|
|
160
|
+
timeouts.push(ret);
|
|
161
|
+
return ret;
|
|
162
|
+
},
|
|
144
163
|
customApiCall,
|
|
145
164
|
notifyClient: frontEndApis_1.notifyClient,
|
|
146
165
|
addBlock: block_1.addBlock,
|
|
@@ -152,6 +171,14 @@ async function initPlugin(pl, morePassedToInit) {
|
|
|
152
171
|
...morePassedToInit
|
|
153
172
|
}));
|
|
154
173
|
Object.assign(pl, typeof res === 'function' ? { unload: res } : res);
|
|
174
|
+
(0, misc_1.patchKey)(pl, 'unload', was => () => {
|
|
175
|
+
for (const x of timeouts)
|
|
176
|
+
clearTimeout(x);
|
|
177
|
+
for (const cb of undoEvents)
|
|
178
|
+
cb();
|
|
179
|
+
if (typeof was === 'function')
|
|
180
|
+
return was(...arguments);
|
|
181
|
+
});
|
|
155
182
|
events_1.default.emit('pluginInitialized', pl);
|
|
156
183
|
return pl;
|
|
157
184
|
}
|
|
@@ -233,6 +260,7 @@ class Plugin {
|
|
|
233
260
|
this.started = new Date();
|
|
234
261
|
if (!data)
|
|
235
262
|
throw 'invalid data';
|
|
263
|
+
this.log = [];
|
|
236
264
|
this.data = data = { ...data }; // clone to make object modifiable. Objects coming from import are not.
|
|
237
265
|
// some validation
|
|
238
266
|
for (const k of ['frontend_css', 'frontend_js']) {
|
|
@@ -479,6 +507,7 @@ function watchPlugin(id, path) {
|
|
|
479
507
|
await (0, promises_1.mkdir)(storageDir, { recursive: true });
|
|
480
508
|
const openDbs = [];
|
|
481
509
|
const subbedConfigs = [];
|
|
510
|
+
const pluginReady = (0, misc_1.pendingPromise)();
|
|
482
511
|
await initPlugin(pluginData, {
|
|
483
512
|
id,
|
|
484
513
|
srcDir: __dirname,
|
|
@@ -493,6 +522,14 @@ function watchPlugin(id, path) {
|
|
|
493
522
|
},
|
|
494
523
|
log(...args) {
|
|
495
524
|
console.log('plugin', id + ':', ...args);
|
|
525
|
+
pluginReady.then(() => {
|
|
526
|
+
if (!plugin)
|
|
527
|
+
return;
|
|
528
|
+
plugin.log.unshift({ ts: new Date, msg: args.map(x => x && typeof x === 'object' ? JSON.stringify(x) : String(x)).join(' ') });
|
|
529
|
+
plugin.log.length = Math.min(100, plugin.log.length); // truncate
|
|
530
|
+
events_1.default.emit('pluginLog:' + id, plugin.log[0]);
|
|
531
|
+
events_1.default.emit('pluginLog', id, plugin.log[0]);
|
|
532
|
+
});
|
|
496
533
|
},
|
|
497
534
|
getConfig(cfgKey) {
|
|
498
535
|
var _a, _b, _c, _d;
|
|
@@ -536,6 +573,7 @@ function watchPlugin(id, path) {
|
|
|
536
573
|
await Promise.allSettled(openDbs.map(x => x.close()));
|
|
537
574
|
openDbs.length = 0;
|
|
538
575
|
});
|
|
576
|
+
pluginReady.resolve();
|
|
539
577
|
if (alreadyRunning)
|
|
540
578
|
events_1.default.emit('pluginUpdated', Object.assign(lodash_1.default.pick(plugin, 'started'), getPluginInfo(id)));
|
|
541
579
|
else {
|
|
@@ -582,13 +620,14 @@ function setError(id, error) {
|
|
|
582
620
|
}
|
|
583
621
|
function deleteModule(id) {
|
|
584
622
|
var _a;
|
|
623
|
+
var _b;
|
|
585
624
|
const { cache } = require;
|
|
586
625
|
// build reversed map of dependencies
|
|
587
626
|
const requiredBy = { '.': ['.'] }; // don't touch main entry
|
|
588
627
|
for (const k in cache)
|
|
589
628
|
if (k !== id)
|
|
590
629
|
for (const child of (0, misc_1.wantArray)((_a = cache[k]) === null || _a === void 0 ? void 0 : _a.children))
|
|
591
|
-
(
|
|
630
|
+
(requiredBy[_b = child.id] || (requiredBy[_b] = [])).push(k);
|
|
592
631
|
const deleted = [];
|
|
593
632
|
(function deleteCache(id) {
|
|
594
633
|
const mod = cache[id];
|
|
@@ -603,7 +642,7 @@ function deleteModule(id) {
|
|
|
603
642
|
}
|
|
604
643
|
(0, first_1.onProcessExit)(() => Promise.allSettled(mapPlugins(pl => pl.unload())));
|
|
605
644
|
function parsePluginSource(id, source) {
|
|
606
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
|
|
645
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
|
|
607
646
|
const pl = { id };
|
|
608
647
|
pl.description = (0, misc_1.tryJson)((_a = /exports.description *= *(".*")/.exec(source)) === null || _a === void 0 ? void 0 : _a[1]);
|
|
609
648
|
pl.repo = (0, misc_1.tryJson)((_b = /exports.repo *= *(.*);? *$/m.exec(source)) === null || _b === void 0 ? void 0 : _b[1]);
|
|
@@ -611,8 +650,9 @@ function parsePluginSource(id, source) {
|
|
|
611
650
|
pl.apiRequired = (_f = (0, misc_1.tryJson)((_e = /exports.apiRequired *= *([ \d.,[\]]+)/.exec(source)) === null || _e === void 0 ? void 0 : _e[1])) !== null && _f !== void 0 ? _f : undefined;
|
|
612
651
|
pl.isTheme = (_h = (0, misc_1.tryJson)((_g = /exports.isTheme *= *(true|false|"light"|"dark")/.exec(source)) === null || _g === void 0 ? void 0 : _g[1])) !== null && _h !== void 0 ? _h : (id.endsWith('-theme') || undefined);
|
|
613
652
|
pl.preview = (_k = (0, misc_1.tryJson)((_j = /exports.preview *= *(.+)/.exec(source)) === null || _j === void 0 ? void 0 : _j[1])) !== null && _k !== void 0 ? _k : undefined;
|
|
614
|
-
pl.depend = (_m = (0, misc_1.tryJson)((_l = /exports.depend *= *(\[
|
|
653
|
+
pl.depend = (_m = (0, misc_1.tryJson)((_l = /exports.depend *= *(\[[\s\S]*?])/m.exec(source)) === null || _l === void 0 ? void 0 : _l[1])) === null || _m === void 0 ? void 0 : _m.filter((x) => typeof x.repo === 'string' && x.version === undefined || typeof x.version === 'number'
|
|
615
654
|
|| console.warn("plugin dependency discarded", x));
|
|
655
|
+
pl.changelog = (0, misc_1.tryJson)((_o = /exports.changelog *= *(\[[\s\S]*?])/m.exec(source)) === null || _o === void 0 ? void 0 : _o[1]);
|
|
616
656
|
if (Array.isArray(pl.apiRequired) && (pl.apiRequired.length !== 2 || !pl.apiRequired.every(lodash_1.default.isFinite))) // validate [from,to] form
|
|
617
657
|
pl.apiRequired = undefined;
|
|
618
658
|
calculateBadApi(pl);
|
package/src/roots.js
CHANGED
|
@@ -42,9 +42,7 @@ const rootsMiddleware = (ctx, next) => (() => {
|
|
|
42
42
|
return true; // true will avoid calling next
|
|
43
43
|
}
|
|
44
44
|
if (!root || root === '/')
|
|
45
|
-
return; //
|
|
46
|
-
if (root === '' || root === '/')
|
|
47
|
-
return;
|
|
45
|
+
return; // no transformation is required
|
|
48
46
|
changeUriParams(v => (0, misc_1.join)(root, v));
|
|
49
47
|
if (!params)
|
|
50
48
|
ctx.path = (0, misc_1.join)(root, ctx.path);
|
|
@@ -20,6 +20,8 @@ const serveGuiFiles_1 = require("./serveGuiFiles");
|
|
|
20
20
|
const koa_mount_1 = __importDefault(require("koa-mount"));
|
|
21
21
|
const listen_1 = require("./listen");
|
|
22
22
|
const misc_1 = require("./misc");
|
|
23
|
+
const promises_1 = require("fs/promises");
|
|
24
|
+
const comments_1 = require("./comments");
|
|
23
25
|
const basicWeb_1 = require("./basicWeb");
|
|
24
26
|
const icons_1 = require("./icons");
|
|
25
27
|
const plugins_1 = require("./plugins");
|
|
@@ -28,7 +30,7 @@ const serveFrontendPrefixed = (0, koa_mount_1.default)(cross_const_1.FRONTEND_UR
|
|
|
28
30
|
const serveAdminFiles = (0, serveGuiFiles_1.serveGuiFiles)(process.env.ADMIN_PROXY, cross_const_1.ADMIN_URI);
|
|
29
31
|
const serveAdminPrefixed = (0, koa_mount_1.default)(cross_const_1.ADMIN_URI.slice(0, -1), serveAdminFiles);
|
|
30
32
|
const serveGuiAndSharedFiles = async (ctx, next) => {
|
|
31
|
-
var _a;
|
|
33
|
+
var _a, _b;
|
|
32
34
|
const { path } = ctx;
|
|
33
35
|
// dynamic import on frontend|admin (used for non-https login) while developing (vite4) is not producing a relative path
|
|
34
36
|
if (const_1.DEV && path.startsWith('/node_modules/')) {
|
|
@@ -108,16 +110,22 @@ const serveGuiAndSharedFiles = async (ctx, next) => {
|
|
|
108
110
|
return;
|
|
109
111
|
}
|
|
110
112
|
if (ctx.method === 'DELETE') {
|
|
111
|
-
const
|
|
112
|
-
if (
|
|
113
|
-
return ctx.status =
|
|
114
|
-
if (
|
|
115
|
-
|
|
113
|
+
const { source } = node;
|
|
114
|
+
if (!source)
|
|
115
|
+
return ctx.status = cross_const_1.HTTP_METHOD_NOT_ALLOWED;
|
|
116
|
+
if ((0, vfs_1.statusCodeForMissingPerm)(node, 'can_delete', ctx))
|
|
117
|
+
return;
|
|
118
|
+
try {
|
|
119
|
+
if ((_b = (await events_1.default.emitAsync('deleting', { node, ctx }))) === null || _b === void 0 ? void 0 : _b.isDefaultPrevented())
|
|
120
|
+
return ctx.status = cross_const_1.HTTP_FAILED_DEPENDENCY;
|
|
121
|
+
await (0, promises_1.rm)(source, { recursive: true });
|
|
122
|
+
void (0, comments_1.setCommentFor)(source, ''); // necessary only to clean a possible descript.ion or kvstorage
|
|
123
|
+
return ctx.status = cross_const_1.HTTP_OK;
|
|
124
|
+
}
|
|
125
|
+
catch (e) {
|
|
126
|
+
ctx.body = String(e);
|
|
116
127
|
return ctx.status = cross_const_1.HTTP_SERVER_ERROR;
|
|
117
128
|
}
|
|
118
|
-
if (res)
|
|
119
|
-
return ctx.status = cross_const_1.HTTP_OK;
|
|
120
|
-
return;
|
|
121
129
|
}
|
|
122
130
|
const { get } = ctx.query;
|
|
123
131
|
if (node.default && path.endsWith('/') && !get) { // final/ needed on browser to make resource urls correctly with html pages
|
|
@@ -164,7 +172,7 @@ async function sendFolderList(node, ctx) {
|
|
|
164
172
|
const { URL } = ctx;
|
|
165
173
|
const base = prepend === undefined && listen_1.baseUrl.get()
|
|
166
174
|
|| URL.protocol + '//' + URL.host + ctx.state.revProxyPath;
|
|
167
|
-
prepend = base + ctx.
|
|
175
|
+
prepend = base + (0, misc_1.pathEncode)(decodeURI(ctx.path)); // redo the encoding our way, keeping unicode chars unchanged
|
|
168
176
|
}
|
|
169
177
|
const walker = (0, vfs_1.walkNode)(node, { ctx, depth: depth === '*' ? Infinity : Number(depth) });
|
|
170
178
|
ctx.body = (0, misc_1.asyncGeneratorToReadable)((0, misc_1.filterMapGenerator)(walker, async (el) => {
|
package/src/serveGuiFiles.js
CHANGED
|
@@ -24,10 +24,8 @@ const size1024 = (0, config_1.defineConfig)(misc_1.CFG.size_1024, false, x => mi
|
|
|
24
24
|
const splitUploads = (0, config_1.defineConfig)(misc_1.CFG.split_uploads, 0);
|
|
25
25
|
exports.logGui = (0, config_1.defineConfig)(misc_1.CFG.log_gui, false);
|
|
26
26
|
lodash_1.default.each(misc_1.FRONTEND_OPTIONS, (v, k) => (0, config_1.defineConfig)(k, v)); // define default values
|
|
27
|
-
// in case of dev env we have our static files within the 'dist' folder'
|
|
28
|
-
const DEV_STATIC = process.env.DEV ? 'dist/' : '';
|
|
29
27
|
function serveStatic(uri) {
|
|
30
|
-
const folder = uri.slice(2, -1); // we know folder is very similar to uri
|
|
28
|
+
const folder = (const_1.DEV ? 'dist/' : '') + uri.slice(2, -1); // we know folder is very similar to uri
|
|
31
29
|
let cache = {};
|
|
32
30
|
customHtml_1.customHtml.emitter.on('change', () => cache = {}); // reset cache at every change
|
|
33
31
|
return async (ctx) => {
|
|
@@ -41,7 +39,7 @@ function serveStatic(uri) {
|
|
|
41
39
|
if (ctx.method !== 'GET')
|
|
42
40
|
return ctx.status = const_1.HTTP_METHOD_NOT_ALLOWED;
|
|
43
41
|
const serveApp = shouldServeApp(ctx);
|
|
44
|
-
const fullPath = (0, path_1.join)(__dirname, '..',
|
|
42
|
+
const fullPath = (0, path_1.join)(__dirname, '..', folder, serveApp ? '/index.html' : ctx.path);
|
|
45
43
|
const content = await (0, misc_1.parseFileContent)(fullPath, raw => serveApp || !raw.length ? raw : adjustBundlerLinks(ctx, uri, raw))
|
|
46
44
|
.catch(() => null);
|
|
47
45
|
if (content === null)
|
package/src/throttler.js
CHANGED
|
@@ -26,6 +26,7 @@ maxKbpsPerIp.sub(v => {
|
|
|
26
26
|
});
|
|
27
27
|
const throttler = async (ctx, next) => {
|
|
28
28
|
var _a;
|
|
29
|
+
var _b;
|
|
29
30
|
await next();
|
|
30
31
|
let { body } = ctx;
|
|
31
32
|
const downloadTotal = ctx.response.length;
|
|
@@ -35,10 +36,10 @@ const throttler = async (ctx, next) => {
|
|
|
35
36
|
return;
|
|
36
37
|
// we wrap the stream also for unlimited connections to get speed and other features
|
|
37
38
|
const noLimit = ((_a = ctx.state.account) === null || _a === void 0 ? void 0 : _a.ignore_limits) || (0, misc_1.isLocalHost)(ctx);
|
|
38
|
-
const ipGroup =
|
|
39
|
+
const ipGroup = ip2group[_b = noLimit ? '' : ctx.ip] || (ip2group[_b] = {
|
|
39
40
|
count: 0,
|
|
40
41
|
group: new ThrottledStream_1.ThrottleGroup(noLimit ? Infinity : maxKbpsPerIp.get(), noLimit ? undefined : mainThrottleGroup),
|
|
41
|
-
})
|
|
42
|
+
});
|
|
42
43
|
const conn = (0, connections_1.getConnection)(ctx);
|
|
43
44
|
if (!conn)
|
|
44
45
|
throw 'assert throttler connection';
|
package/src/update.js
CHANGED
|
@@ -125,7 +125,7 @@ async function update(tagOrUrl = '') {
|
|
|
125
125
|
const asset = update.assets.find((x) => x.name.includes(assetSearch) && x.name.endsWith('.zip'))
|
|
126
126
|
|| update.assets.find((x) => x.name.endsWith(legacyAssetSearch));
|
|
127
127
|
if (!asset)
|
|
128
|
-
throw
|
|
128
|
+
throw `asset not found: ${assetSearch}`;
|
|
129
129
|
url = asset.browser_download_url;
|
|
130
130
|
}
|
|
131
131
|
if (url) {
|
package/src/upload.js
CHANGED
|
@@ -105,7 +105,7 @@ function uploadWriter(base, baseUri, path, ctx) {
|
|
|
105
105
|
if (typeof free !== 'number' || isNaN(free))
|
|
106
106
|
throw '';
|
|
107
107
|
if (fullSize > free - (min || 0))
|
|
108
|
-
return fail(const_1.
|
|
108
|
+
return fail(const_1.HTTP_INSUFFICIENT_STORAGE);
|
|
109
109
|
}
|
|
110
110
|
catch (e) { // warn, but let it through
|
|
111
111
|
console.warn("can't check disk size:", e.message || String(e));
|
package/src/vfs.js
CHANGED
|
@@ -300,7 +300,7 @@ async function* walkNode(parent, { ctx, depth = Infinity, prefixPath = '', requi
|
|
|
300
300
|
const { path } = entry;
|
|
301
301
|
const isFolder = entry.isDirectory();
|
|
302
302
|
const name = prefixPath + (((_a = parent.rename) === null || _a === void 0 ? void 0 : _a[path]) || path);
|
|
303
|
-
if (comments_1.
|
|
303
|
+
if ((0, comments_1.usingDescriptIon)() && (0, path_1.basename)(name) === comments_1.DESCRIPT_ION)
|
|
304
304
|
return;
|
|
305
305
|
if (taken === null || taken === void 0 ? void 0 : taken.has(normalizeFilename(name))) // taken by vfs node above
|
|
306
306
|
return false; // false just in case it's a folder
|
package/README.md
DELETED
|
@@ -1,177 +0,0 @@
|
|
|
1
|
-
# HFS: HTTP File Server (version 3)
|
|
2
|
-
|
|
3
|
-

|
|
4
|
-
|
|
5
|
-
## Introduction
|
|
6
|
-
|
|
7
|
-
Access via web your files directly from your disk.
|
|
8
|
-
|
|
9
|
-
- You be the server, share files **fresh from your disk**, with **unlimited** space and bandwidth.
|
|
10
|
-
- **Fast!** Try zipping 100GB, download starts immediately!
|
|
11
|
-
- **Intelligent**. HFS tries to detect problems and suggest solutions.
|
|
12
|
-
- Share **even a single file** with our *virtual file system*, even with a different name, all without touching the real file. Present things the way you want!
|
|
13
|
-
- **Watch** all activities in real-time.
|
|
14
|
-
- **Control bandwidth**, decide how much to give.
|
|
15
|
-
- **No intermediaries**, give a huge file to your friend without waiting for it to be uploaded on a server first.
|
|
16
|
-
|
|
17
|
-
This is a full rewrite of [the Delphi version](https://github.com/rejetto/hfs2).
|
|
18
|
-
|
|
19
|
-
## How does it work
|
|
20
|
-
|
|
21
|
-
- run HFS on your computer, administration page automatically shows up
|
|
22
|
-
- select what files and folders you want to be accessible
|
|
23
|
-
- access those files from a phone or another computer just using a browser
|
|
24
|
-
- possibly create accounts and limit access to files
|
|
25
|
-
|
|
26
|
-
## Features
|
|
27
|
-
|
|
28
|
-
- https
|
|
29
|
-
- easy certificate generation
|
|
30
|
-
- unicode
|
|
31
|
-
- virtual file system
|
|
32
|
-
- mobile friendly
|
|
33
|
-
- search
|
|
34
|
-
- accounts
|
|
35
|
-
- resumable downloads & uploads
|
|
36
|
-
- download folders as zip archive
|
|
37
|
-
- delete, move and rename files
|
|
38
|
-
- simple website serving
|
|
39
|
-
- plug-ins
|
|
40
|
-
- real-time monitoring of connections
|
|
41
|
-
- [show some files](https://github.com/rejetto/hfs/discussions/270)
|
|
42
|
-
- speed throttler
|
|
43
|
-
- geographic firewall
|
|
44
|
-
- admin web interface
|
|
45
|
-
- multi-language front-end
|
|
46
|
-
- virtual hosting
|
|
47
|
-
- anti-brute-force (plug-in)
|
|
48
|
-
- [reverse-proxy support](https://github.com/rejetto/hfs/wiki/Reverse-proxy)
|
|
49
|
-
- comments in file descript.ion
|
|
50
|
-
- integrated media player
|
|
51
|
-
- [customizable with html, css and javascript](https://github.com/rejetto/hfs/wiki/Customization)
|
|
52
|
-
- dynamic-dns updater
|
|
53
|
-
|
|
54
|
-
## Installation
|
|
55
|
-
|
|
56
|
-
For service installation instructions, [see our wiki](https://github.com/rejetto/hfs/wiki/Service-installation).
|
|
57
|
-
|
|
58
|
-
For Docker installation, [see dedicated repo](https://github.com/damienzonly/hfs-docker).
|
|
59
|
-
|
|
60
|
-
NB: minimum Windows version required is 8.1 , Windows Server 2012 R2 (because of Node.js 18)
|
|
61
|
-
|
|
62
|
-
1. go to https://github.com/rejetto/hfs/releases
|
|
63
|
-
2. click on `Assets`
|
|
64
|
-
3. **download** the right version for your system, unzip and launch `hfs` file.
|
|
65
|
-
- Mac: if you get *"cannot be opened because it is from an unidentified developer"*,
|
|
66
|
-
you can hold `control` key while clicking, then click `open`.
|
|
67
|
-
- If you cannot find your system in the list, see next section [Other systems](#other-systems).
|
|
68
|
-
4. the browser should automatically open on `localhost` address, so you can configure the rest in the Admin-panel.
|
|
69
|
-
- if a browser cannot be opened on the computer where you are installing HFS,
|
|
70
|
-
you should enter this command in the HFS console: `create-admin <PASSWORD>`
|
|
71
|
-
- if you cannot access the console (like when you are running as a service),
|
|
72
|
-
you can [edit the config file to add your admin account](config.md#accounts)
|
|
73
|
-
- if you don't want to use an editor you can create the file with this command:
|
|
74
|
-
|
|
75
|
-
`echo "create-admin: PASSWORD" > config.yaml`
|
|
76
|
-
|
|
77
|
-
If you access *Admin-panel* via localhost, by default HFS **won't** require you to login.
|
|
78
|
-
If you don't like this behavior, disable it in the Admin-panel or enter this console command `config localhost_admin false`.
|
|
79
|
-
|
|
80
|
-
### Other systems
|
|
81
|
-
|
|
82
|
-
If your system is not Windows/Linux/Mac or you just don't want to run the binaries, you can try this alternative version:
|
|
83
|
-
|
|
84
|
-
1. [install node.js](https://nodejs.org) version 18.20
|
|
85
|
-
2. execute at command line `npx hfs@latest`
|
|
86
|
-
|
|
87
|
-
The `@latest` part is optional, and ensures that you are always up to date.
|
|
88
|
-
|
|
89
|
-
If this procedure fails, it may be that you are missing one of [these requirements](https://github.com/nodejs/node-gyp#installation).
|
|
90
|
-
|
|
91
|
-
Configuration and other files will be stored in `%HOME%/.vfs`
|
|
92
|
-
|
|
93
|
-
## Console commands
|
|
94
|
-
|
|
95
|
-
If you have full access to HFS' console, you can enter commands. Start with `help` to have a full list.
|
|
96
|
-
|
|
97
|
-
## Configuration
|
|
98
|
-
|
|
99
|
-
For configuration please see [file config.md](config.md).
|
|
100
|
-
|
|
101
|
-
### Where is it stored
|
|
102
|
-
|
|
103
|
-
Configuration is stored in the file `config.yaml`, exception made for custom HTML which is stored in `custom.html`.
|
|
104
|
-
|
|
105
|
-
These files are kept in the Current Working Directory (cwd), which is by default the same folder of `hfs.exe`
|
|
106
|
-
if you are using this kind of distribution on Windows, or `USER_FOLDER/.hfs` on other systems.
|
|
107
|
-
You can decide a different folder passing `--cwd SOME_FOLDER` parameter at command line.
|
|
108
|
-
Any relative path provided is relative to the *cwd*.
|
|
109
|
-
|
|
110
|
-
[Check details about config file format](config.md).
|
|
111
|
-
|
|
112
|
-
## Internationalization
|
|
113
|
-
|
|
114
|
-
It is possible to show the Front-end in other languages.
|
|
115
|
-
Translation for some languages is already provided. If you find an error, consider reporting it
|
|
116
|
-
or [editing the source file](https://github.com/rejetto/hfs/tree/main/src/langs).
|
|
117
|
-
|
|
118
|
-
In the Languages section of the Admin-panel you can install additional language files.
|
|
119
|
-
|
|
120
|
-
If your language is missing, please consider [translating yourself](https://github.com/rejetto/hfs/wiki/Translation).
|
|
121
|
-
|
|
122
|
-
## Hidden features
|
|
123
|
-
|
|
124
|
-
- Appending `#LOGIN` to address will bring up the login dialog
|
|
125
|
-
- Appending ?lang=CODE to address will force a specific language
|
|
126
|
-
- Right-click on toggle-all checkbox will invert each checkbox state
|
|
127
|
-
- Appending `?login=USER:PASSWORD` will automatically log in the browser
|
|
128
|
-
- Appending `?overwrite` on uploads, will override the dont_overwrite_uploading configuration, provided you also have delete permission
|
|
129
|
-
- Appending `?search=PATTERN` will trigger search at start
|
|
130
|
-
- Appending `?onlyFiles` or `?onlyFolders` will limit type of results
|
|
131
|
-
- Appending `?autoplay=shuffle` will trigger show & play; `?autoplay` will not shuffle, but also will not start until the list is complete
|
|
132
|
-
- Right-click on "check for updates" will let you input a URL of a version to install
|
|
133
|
-
- Shift+click on a file will show & play
|
|
134
|
-
- Type the name of a file/folder to focus it, and ctrl+backspace to go to parent folder
|
|
135
|
-
- `--consoleFile PATH` will output all stdout and stderr also to a file
|
|
136
|
-
- env `DISABLE_UPDATE=1` (for containers)
|
|
137
|
-
|
|
138
|
-
## Contribute
|
|
139
|
-
|
|
140
|
-
There are several ways to contribute
|
|
141
|
-
|
|
142
|
-
- [Report bugs](https://github.com/rejetto/hfs/issues/new?labels=bug&template=bug_report.md)
|
|
143
|
-
|
|
144
|
-
It's very important to report bugs, and if you are not so sure about it, don't worry, we'll discuss it.
|
|
145
|
-
If you find important security problems, please [contact us privately](mailto:a@rejetto.com) so that we can publish a fix before
|
|
146
|
-
the problem is disclosed, for the safety of other users.
|
|
147
|
-
|
|
148
|
-
- Use beta versions, and give feedback.
|
|
149
|
-
|
|
150
|
-
While betas have more problems, you'll get more features and give a huge help to the project.
|
|
151
|
-
|
|
152
|
-
- [Translate to your language](https://github.com/rejetto/hfs/wiki/Translation).
|
|
153
|
-
|
|
154
|
-
- [Suggest ideas](https://github.com/rejetto/hfs/discussions)
|
|
155
|
-
|
|
156
|
-
While the project should not become too complex, yours may be an idea for a plugin.
|
|
157
|
-
|
|
158
|
-
- Write guides or make videos for other users. [We got a wiki](https://github.com/rejetto/hfs/wiki)!
|
|
159
|
-
|
|
160
|
-
- Submit your code
|
|
161
|
-
|
|
162
|
-
If you'd like to make a change yourself in the code, please first open an "issue" or "discussion" about it,
|
|
163
|
-
so we'll try to cooperate and understand what's the best path for it.
|
|
164
|
-
|
|
165
|
-
- [Make a plugin](dev-plugins.md)
|
|
166
|
-
|
|
167
|
-
A plugin can change the look (a theme), and/or introduce a new functionality.
|
|
168
|
-
|
|
169
|
-
## More
|
|
170
|
-
|
|
171
|
-
- [APIs](https://github.com/rejetto/hfs/wiki/APIs)
|
|
172
|
-
|
|
173
|
-
- [Build yourself](dev.md)
|
|
174
|
-
|
|
175
|
-
- [License](https://github.com/rejetto/hfs/blob/master/LICENSE.txt)
|
|
176
|
-
|
|
177
|
-
- Flag images are public-domain, downloaded from https://flagpedia.net
|