hfs 0.55.4 → 0.56.0-alpha2
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-Cps81be4.js +819 -0
- package/admin/assets/index-D3kXFfFt.css +1 -0
- package/admin/assets/sha512-CjwnYWMV.js +8 -0
- package/admin/index.html +2 -2
- package/frontend/assets/index-legacy-B70uZPX1.js +50 -0
- package/frontend/assets/polyfills-legacy-Ca1TOBqp.js +1 -0
- package/frontend/assets/sha512-legacy-gnUzOTiL.js +9 -0
- package/frontend/index.html +2 -2
- package/package.json +1 -1
- package/src/SendList.js +1 -1
- package/src/adminApis.js +1 -1
- package/src/api.auth.js +2 -0
- package/src/api.get_file_list.js +2 -4
- package/src/api.vfs.js +11 -16
- package/src/auth.js +1 -0
- package/src/basicWeb.js +1 -1
- package/src/commands.js +3 -6
- package/src/config.js +32 -11
- package/src/const.js +2 -2
- package/src/cross-const.js +2 -1
- package/src/cross.js +1 -1
- package/src/debounceAsync.js +3 -2
- package/src/dirStream.js +1 -1
- package/src/frontEndApis.js +23 -0
- package/src/icons.js +35 -0
- package/src/index.js +3 -0
- package/src/langs/hfs-lang-hu.json +10 -6
- package/src/langs/hfs-lang-it.json +2 -1
- package/src/log.js +1 -1
- package/src/middlewares.js +11 -2
- package/src/misc.js +7 -1
- package/src/nat.js +1 -1
- package/src/outboundProxy.js +43 -0
- package/src/plugins.js +25 -14
- package/src/serveGuiAndSharedFiles.js +15 -0
- package/src/serveGuiFiles.js +5 -0
- package/src/update.js +1 -1
- package/src/upload.js +12 -1
- package/src/util-files.js +3 -1
- package/src/util-http.js +14 -7
- package/src/util-os.js +5 -1
- package/src/vfs.js +28 -9
- package/src/watchLoad.js +3 -2
- package/src/zip.js +4 -2
- package/admin/assets/index-C840XOsq.js +0 -809
- package/admin/assets/index-CrbMquLL.css +0 -1
- package/admin/assets/sha512-CgS2_xLX.js +0 -8
- package/frontend/assets/index-legacy-ckp4YJR7.js +0 -60
- package/frontend/assets/polyfills-legacy-DMrMt_pQ.js +0 -4
- package/frontend/assets/sha512-legacy-FLDdvu6g.js +0 -9
package/src/log.js
CHANGED
|
@@ -56,7 +56,7 @@ class Logger {
|
|
|
56
56
|
return this.stream = undefined;
|
|
57
57
|
try {
|
|
58
58
|
const stats = await (0, promises_1.stat)(path);
|
|
59
|
-
this.last = stats.mtime || stats.
|
|
59
|
+
this.last = stats.mtime || stats.birthtime;
|
|
60
60
|
}
|
|
61
61
|
catch (_b) {
|
|
62
62
|
if (await (0, util_files_1.prepareFolder)(path) === false)
|
package/src/middlewares.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.sessionMiddleware = exports.paramsDecoder = exports.prepareState = exports.getProxyDetected = exports.someSecurity = exports.cloudflareDetected = exports.headRequests = exports.gzipper = exports.sessionDuration = void 0;
|
|
7
|
+
exports.sessionMiddleware = exports.paramsDecoder = exports.failAllowNet = exports.prepareState = exports.getProxyDetected = exports.someSecurity = exports.cloudflareDetected = exports.headRequests = exports.gzipper = exports.sessionDuration = void 0;
|
|
8
8
|
const koa_compress_1 = __importDefault(require("koa-compress"));
|
|
9
9
|
const const_1 = require("./const");
|
|
10
10
|
const misc_1 = require("./misc");
|
|
@@ -106,7 +106,7 @@ const prepareState = async (ctx, next) => {
|
|
|
106
106
|
// calculate these once and for all
|
|
107
107
|
ctx.state.connection = (0, connections_1.socket2connection)(ctx.socket);
|
|
108
108
|
const a = ctx.state.account = await urlLogin() || await getHttpAccount() || (0, perm_1.getAccount)((_b = ctx.session) === null || _b === void 0 ? void 0 : _b.username, false);
|
|
109
|
-
if (a && !(0, perm_1.accountCanLogin)(a))
|
|
109
|
+
if (a && (!(0, perm_1.accountCanLogin)(a) || failAllowNet(ctx, a))) // enforce allow_net also after login
|
|
110
110
|
ctx.state.account = undefined;
|
|
111
111
|
ctx.state.revProxyPath = ctx.get('x-forwarded-prefix');
|
|
112
112
|
(0, connections_1.updateConnectionForCtx)(ctx);
|
|
@@ -147,6 +147,15 @@ const prepareState = async (ctx, next) => {
|
|
|
147
147
|
}
|
|
148
148
|
};
|
|
149
149
|
exports.prepareState = prepareState;
|
|
150
|
+
function failAllowNet(ctx, a) {
|
|
151
|
+
var _a, _b;
|
|
152
|
+
const cached = (_a = ctx.session) === null || _a === void 0 ? void 0 : _a.allowNet; // won't reflect changes until session is terminated
|
|
153
|
+
const mask = cached !== null && cached !== void 0 ? cached : (0, perm_1.getFromAccount)(a || '', a => a.allow_net);
|
|
154
|
+
if (!cached && mask && ((_b = ctx.session) === null || _b === void 0 ? void 0 : _b.username))
|
|
155
|
+
ctx.session.allowNet = mask; // must be deleted on logout by setLoggedIn
|
|
156
|
+
return mask && !(0, misc_1.netMatches)(ctx.ip, mask, true);
|
|
157
|
+
}
|
|
158
|
+
exports.failAllowNet = failAllowNet;
|
|
150
159
|
const paramsDecoder = async (ctx, next) => {
|
|
151
160
|
ctx.state.params = ctx.method === 'POST' && ctx.originalUrl.startsWith(const_1.API_URI)
|
|
152
161
|
&& ((0, misc_1.tryJson)(await (0, misc_1.stream2string)(ctx.req)) || {});
|
package/src/misc.js
CHANGED
|
@@ -18,7 +18,7 @@ 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.deleteNode = exports.createStreamLimiter = exports.apiAssertTypes = exports.AsapStream = exports.asyncGeneratorToReadable = exports.same = exports.makeNetMatcher = exports.isLocalHost = exports.pattern2filter = void 0;
|
|
21
|
+
exports.deleteNode = 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
23
|
const assert_1 = __importDefault(require("assert"));
|
|
24
24
|
__exportStar(require("./util-http"), exports);
|
|
@@ -36,6 +36,7 @@ const vfs_1 = require("./vfs");
|
|
|
36
36
|
const events_1 = __importDefault(require("./events"));
|
|
37
37
|
const promises_1 = require("fs/promises");
|
|
38
38
|
const comments_1 = require("./comments");
|
|
39
|
+
const lodash_1 = __importDefault(require("lodash"));
|
|
39
40
|
function pattern2filter(pattern) {
|
|
40
41
|
const matcher = (0, cross_1.makeMatcher)(pattern.includes('*') ? pattern // if you specify *, we'll respect its position
|
|
41
42
|
: pattern.split('|').map(x => `*${x}*`).join('|'));
|
|
@@ -47,6 +48,11 @@ function isLocalHost(c) {
|
|
|
47
48
|
return ip && (0, cross_1.isIpLocalHost)(ip);
|
|
48
49
|
}
|
|
49
50
|
exports.isLocalHost = isLocalHost;
|
|
51
|
+
// this will memory-leak over mask, so be careful with what you use this
|
|
52
|
+
function netMatches(ip, mask, emptyMaskReturns = false) {
|
|
53
|
+
return lodash_1.default.memoize(makeNetMatcher, (a, b) => `${a}\t${b ? 1 : 0}`)(mask, emptyMaskReturns)(ip); // cache the matcher
|
|
54
|
+
}
|
|
55
|
+
exports.netMatches = netMatches;
|
|
50
56
|
function makeNetMatcher(mask, emptyMaskReturns = false) {
|
|
51
57
|
var _a;
|
|
52
58
|
if (!mask)
|
package/src/nat.js
CHANGED
|
@@ -92,7 +92,7 @@ exports.getNatInfo = (0, debounceAsync_1.debounceAsync)(async () => {
|
|
|
92
92
|
externalPort,
|
|
93
93
|
proto: ((_c = status === null || status === void 0 ? void 0 : status.https) === null || _c === void 0 ? void 0 : _c.listening) ? 'https' : ((_d = status === null || status === void 0 ? void 0 : status.http) === null || _d === void 0 ? void 0 : _d.listening) ? 'http' : '',
|
|
94
94
|
};
|
|
95
|
-
});
|
|
95
|
+
}, { reuseRunning: true });
|
|
96
96
|
(0, exports.getNatInfo)();
|
|
97
97
|
function findGateway() {
|
|
98
98
|
return new Promise((resolve, reject) => (0, child_process_1.exec)(const_1.IS_WINDOWS || const_1.IS_MAC ? 'netstat -rn' : 'route -n', (err, out) => {
|
|
@@ -0,0 +1,43 @@
|
|
|
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
|
+
const config_1 = require("./config");
|
|
7
|
+
const node_url_1 = require("node:url");
|
|
8
|
+
const util_http_1 = require("./util-http");
|
|
9
|
+
const util_os_1 = require("./util-os");
|
|
10
|
+
const events_1 = __importDefault(require("./events"));
|
|
11
|
+
const const_1 = require("./const");
|
|
12
|
+
const cross_1 = require("./cross");
|
|
13
|
+
// don't move this in util-http, where it would mostly belong, as a require to config.ts would prevent tests using util-http
|
|
14
|
+
const outboundProxy = (0, config_1.defineConfig)('outbound_proxy', '', v => {
|
|
15
|
+
try {
|
|
16
|
+
(0, node_url_1.parse)(v);
|
|
17
|
+
util_http_1.httpStream.defaultProxy = v;
|
|
18
|
+
}
|
|
19
|
+
catch (_a) {
|
|
20
|
+
console.warn("invalid URL", v);
|
|
21
|
+
return '';
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
events_1.default.once('configReady', async (startedWithoutConfig) => {
|
|
25
|
+
var _a, _b, _c, _d;
|
|
26
|
+
if (!const_1.IS_WINDOWS || !startedWithoutConfig)
|
|
27
|
+
return;
|
|
28
|
+
// try to read Windows system setting for proxy
|
|
29
|
+
const out = await (0, util_os_1.reg)('query', 'HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings');
|
|
30
|
+
if (!((_a = /ProxyEnable.+(\d)/.exec(out)) === null || _a === void 0 ? void 0 : _a[1]))
|
|
31
|
+
return;
|
|
32
|
+
const read = (_b = /ProxyServer.+?([\d:.]+)/.exec(out)) === null || _b === void 0 ? void 0 : _b[1];
|
|
33
|
+
if (!read)
|
|
34
|
+
return;
|
|
35
|
+
// it can be like "IP:PORT" or "http=IP:PORT;https=IP:PORT;ftp=IP:PORT"
|
|
36
|
+
const url = (0, cross_1.prefix)('https://', (_c = /https=([\d:.]+)/.exec(out)) === null || _c === void 0 ? void 0 : _c[1]) // prefer https
|
|
37
|
+
|| (0, cross_1.prefix)('http://', (_d = /http=([\d:.]+)/.exec(out)) === null || _d === void 0 ? void 0 : _d[1])
|
|
38
|
+
|| !read.includes('=') && 'http://' + read; // simpler form
|
|
39
|
+
if (!url)
|
|
40
|
+
return;
|
|
41
|
+
outboundProxy.set(url);
|
|
42
|
+
console.log("detected proxy", read);
|
|
43
|
+
});
|
package/src/plugins.js
CHANGED
|
@@ -52,6 +52,7 @@ const lang_1 = require("./lang");
|
|
|
52
52
|
const i18n_1 = require("./i18n");
|
|
53
53
|
const perm_1 = require("./perm");
|
|
54
54
|
const auth_1 = require("./auth");
|
|
55
|
+
const icons_1 = require("./icons");
|
|
55
56
|
exports.PATH = 'plugins';
|
|
56
57
|
exports.DISABLING_SUFFIX = '-disabled';
|
|
57
58
|
exports.STORAGE_FOLDER = 'storage';
|
|
@@ -110,9 +111,8 @@ function setPluginConfig(id, changes) {
|
|
|
110
111
|
}
|
|
111
112
|
exports.setPluginConfig = setPluginConfig;
|
|
112
113
|
function getPluginInfo(id) {
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
return running && Object.assign(running, { id }) || availablePlugins[id];
|
|
114
|
+
const running = plugins.get(id);
|
|
115
|
+
return running && { ...running.getData(), ...running } || availablePlugins[id];
|
|
116
116
|
}
|
|
117
117
|
exports.getPluginInfo = getPluginInfo;
|
|
118
118
|
function findPluginByRepo(repo) {
|
|
@@ -139,6 +139,7 @@ async function initPlugin(pl, morePassedToInit) {
|
|
|
139
139
|
getConnections: connections_1.getConnections,
|
|
140
140
|
events: events_1.default,
|
|
141
141
|
log: console.log,
|
|
142
|
+
setError(msg) { setError((morePassedToInit === null || morePassedToInit === void 0 ? void 0 : morePassedToInit.id) || 'server_code', msg); },
|
|
142
143
|
getHfsConfig: config_1.getConfig,
|
|
143
144
|
customApiCall,
|
|
144
145
|
notifyClient: frontEndApis_1.notifyClient,
|
|
@@ -148,7 +149,9 @@ async function initPlugin(pl, morePassedToInit) {
|
|
|
148
149
|
getCurrentUsername: auth_1.getCurrentUsername,
|
|
149
150
|
...morePassedToInit
|
|
150
151
|
}));
|
|
151
|
-
|
|
152
|
+
Object.assign(pl, typeof res === 'function' ? { unload: res } : res);
|
|
153
|
+
events_1.default.emit('pluginInitialized', pl);
|
|
154
|
+
return pl;
|
|
152
155
|
}
|
|
153
156
|
const already = new Set();
|
|
154
157
|
function warnOnce(msg) {
|
|
@@ -208,12 +211,12 @@ const pluginsMiddleware = async (ctx, next) => {
|
|
|
208
211
|
catch (e) {
|
|
209
212
|
printError(id, e);
|
|
210
213
|
}
|
|
214
|
+
function printError(id, e) {
|
|
215
|
+
console.log(`error middleware plugin ${id}: ${(e === null || e === void 0 ? void 0 : e.message) || e}`);
|
|
216
|
+
console.debug(e);
|
|
217
|
+
}
|
|
211
218
|
};
|
|
212
219
|
exports.pluginsMiddleware = pluginsMiddleware;
|
|
213
|
-
function printError(id, e) {
|
|
214
|
-
console.log(`error middleware plugin ${id}: ${(e === null || e === void 0 ? void 0 : e.message) || e}`);
|
|
215
|
-
console.debug(e);
|
|
216
|
-
}
|
|
217
220
|
events_1.default.once('app', () => Object.assign(index_1.app.context, {
|
|
218
221
|
isStopped: false,
|
|
219
222
|
stop() { return this.isStopped = true; }
|
|
@@ -280,7 +283,7 @@ class Plugin {
|
|
|
280
283
|
return (_a = this.data) === null || _a === void 0 ? void 0 : _a.onDirEntry;
|
|
281
284
|
}
|
|
282
285
|
getData() {
|
|
283
|
-
return
|
|
286
|
+
return this.data;
|
|
284
287
|
}
|
|
285
288
|
async unload(reloading = false) {
|
|
286
289
|
var _a, _b;
|
|
@@ -425,9 +428,8 @@ function watchPlugin(id, path) {
|
|
|
425
428
|
};
|
|
426
429
|
async function onUninstalled() {
|
|
427
430
|
await stop();
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
return;
|
|
431
|
+
if (!getPluginInfo(id))
|
|
432
|
+
return; // already missing
|
|
431
433
|
delete availablePlugins[id];
|
|
432
434
|
events_1.default.emit('pluginUninstalled', id);
|
|
433
435
|
}
|
|
@@ -474,6 +476,7 @@ function watchPlugin(id, path) {
|
|
|
474
476
|
await (0, promises_1.mkdir)(storageDir, { recursive: true });
|
|
475
477
|
const dbs = [];
|
|
476
478
|
await initPlugin(pluginData, {
|
|
479
|
+
id,
|
|
477
480
|
srcDir: __dirname,
|
|
478
481
|
storageDir,
|
|
479
482
|
async openDb(filename, options) {
|
|
@@ -516,7 +519,9 @@ function watchPlugin(id, path) {
|
|
|
516
519
|
const folder = (0, path_1.dirname)(module);
|
|
517
520
|
const { sections, unwatch } = (0, customHtml_1.watchLoadCustomHtml)(folder);
|
|
518
521
|
pluginData.getCustomHtml = () => Object.assign(Object.fromEntries(sections), (0, misc_1.callable)(pluginData.customHtml) || {});
|
|
522
|
+
const unwatchIcons = (0, icons_1.watchIconsFolder)(folder, v => plugin.icons = v);
|
|
519
523
|
const plugin = new Plugin(id, folder, pluginData, async () => {
|
|
524
|
+
unwatchIcons();
|
|
520
525
|
unwatch();
|
|
521
526
|
await Promise.allSettled(dbs.map(x => x.close()));
|
|
522
527
|
dbs.length = 0;
|
|
@@ -536,7 +541,6 @@ function watchPlugin(id, path) {
|
|
|
536
541
|
const parsed = (_a = e.stack) === null || _a === void 0 ? void 0 : _a.split('\n\n'); // this form is used by syntax-errors inside the plugin, which is useful to show
|
|
537
542
|
const where = (parsed === null || parsed === void 0 ? void 0 : parsed.length) > 1 ? `\n${parsed[0]}` : '';
|
|
538
543
|
e = e.message + where || String(e);
|
|
539
|
-
console.log(`plugin error: ${id}:`, e);
|
|
540
544
|
setError(id, e);
|
|
541
545
|
}
|
|
542
546
|
finally {
|
|
@@ -552,12 +556,19 @@ function getError(id) {
|
|
|
552
556
|
var _a;
|
|
553
557
|
return (_a = getPluginInfo(id)) === null || _a === void 0 ? void 0 : _a.error;
|
|
554
558
|
}
|
|
559
|
+
// returns true if there's an error, and it has changed
|
|
555
560
|
function setError(id, error) {
|
|
556
561
|
const info = getPluginInfo(id);
|
|
557
562
|
if (!info)
|
|
558
563
|
return;
|
|
564
|
+
if (info.error === error)
|
|
565
|
+
return;
|
|
559
566
|
info.error = error;
|
|
560
|
-
events_1.default.emit('pluginUpdated',
|
|
567
|
+
events_1.default.emit('pluginUpdated', info);
|
|
568
|
+
if (!error)
|
|
569
|
+
return;
|
|
570
|
+
console.log(`plugin error: ${id}:`, error);
|
|
571
|
+
return true;
|
|
561
572
|
}
|
|
562
573
|
function deleteModule(id) {
|
|
563
574
|
var _a;
|
|
@@ -21,11 +21,14 @@ const koa_mount_1 = __importDefault(require("koa-mount"));
|
|
|
21
21
|
const listen_1 = require("./listen");
|
|
22
22
|
const misc_1 = require("./misc");
|
|
23
23
|
const basicWeb_1 = require("./basicWeb");
|
|
24
|
+
const icons_1 = require("./icons");
|
|
25
|
+
const plugins_1 = require("./plugins");
|
|
24
26
|
const serveFrontendFiles = (0, serveGuiFiles_1.serveGuiFiles)(process.env.FRONTEND_PROXY, cross_const_1.FRONTEND_URI);
|
|
25
27
|
const serveFrontendPrefixed = (0, koa_mount_1.default)(cross_const_1.FRONTEND_URI.slice(0, -1), serveFrontendFiles);
|
|
26
28
|
const serveAdminFiles = (0, serveGuiFiles_1.serveGuiFiles)(process.env.ADMIN_PROXY, cross_const_1.ADMIN_URI);
|
|
27
29
|
const serveAdminPrefixed = (0, koa_mount_1.default)(cross_const_1.ADMIN_URI.slice(0, -1), serveAdminFiles);
|
|
28
30
|
const serveGuiAndSharedFiles = async (ctx, next) => {
|
|
31
|
+
var _a;
|
|
29
32
|
const { path } = ctx;
|
|
30
33
|
// dynamic import on frontend|admin (used for non-https login) while developing (vite4) is not producing a relative path
|
|
31
34
|
if (const_1.DEV && path.startsWith('/node_modules/')) {
|
|
@@ -40,6 +43,18 @@ const serveGuiAndSharedFiles = async (ctx, next) => {
|
|
|
40
43
|
if (path.startsWith(cross_const_1.ADMIN_URI))
|
|
41
44
|
return (0, adminApis_1.allowAdmin)(ctx) ? serveAdminPrefixed(ctx, next)
|
|
42
45
|
: (0, errorPages_1.sendErrorPage)(ctx, cross_const_1.HTTP_FORBIDDEN);
|
|
46
|
+
if (path.startsWith(cross_const_1.ICONS_URI)) {
|
|
47
|
+
const a = path.substring(cross_const_1.ICONS_URI.length).split('/');
|
|
48
|
+
const iconName = a.at(-1);
|
|
49
|
+
if (!iconName)
|
|
50
|
+
return;
|
|
51
|
+
const plugin = a.length > 1 && (0, plugins_1.getPluginInfo)(a[0]); // an extra level in the path indicates a plugin
|
|
52
|
+
const file = plugin ? (_a = plugin.icons) === null || _a === void 0 ? void 0 : _a[iconName] : icons_1.customizedIcons === null || icons_1.customizedIcons === void 0 ? void 0 : icons_1.customizedIcons[iconName];
|
|
53
|
+
if (!file)
|
|
54
|
+
return;
|
|
55
|
+
ctx.state.considerAsGui = true;
|
|
56
|
+
return (0, serveFile_1.serveFile)(ctx, (0, path_1.join)((plugin === null || plugin === void 0 ? void 0 : plugin.folder) || '', icons_1.ICONS_FOLDER, file), const_1.MIME_AUTO);
|
|
57
|
+
}
|
|
43
58
|
if (ctx.method === 'PUT') { // curl -T file url/
|
|
44
59
|
const decPath = decodeURIComponent(path);
|
|
45
60
|
let rest = (0, path_1.basename)(decPath);
|
package/src/serveGuiFiles.js
CHANGED
|
@@ -19,6 +19,7 @@ const lodash_1 = __importDefault(require("lodash"));
|
|
|
19
19
|
const config_1 = require("./config");
|
|
20
20
|
const lang_1 = require("./lang");
|
|
21
21
|
const upload_1 = require("./upload");
|
|
22
|
+
const icons_1 = require("./icons");
|
|
22
23
|
const size1024 = (0, config_1.defineConfig)(misc_1.CFG.size_1024, false, x => misc_1.formatBytes.k = x ? 1024 : 1000); // we both configure formatBytes, and also provide a compiled version (number instead of boolean)
|
|
23
24
|
const splitUploads = (0, config_1.defineConfig)(misc_1.CFG.split_uploads, 0);
|
|
24
25
|
exports.logGui = (0, config_1.defineConfig)(misc_1.CFG.log_gui, false);
|
|
@@ -107,6 +108,7 @@ async function treatIndex(ctx, filesUri, body) {
|
|
|
107
108
|
forceTheme: (0, plugins_1.mapPlugins)(p => lodash_1.default.isString(p.isTheme) ? p.isTheme : undefined).find(Boolean),
|
|
108
109
|
customHtml: lodash_1.default.omit((0, customHtml_1.getAllSections)(), ['top', 'bottom', 'htmlHead', 'style']),
|
|
109
110
|
...(0, misc_1.newObj)(misc_1.FRONTEND_OPTIONS, (v, k) => (0, config_1.getConfig)(k)),
|
|
111
|
+
icons: Object.assign({}, ...(0, plugins_1.mapPlugins)(p => iconsToObj(p.icons, p.id + '/')), iconsToObj(icons_1.customizedIcons)),
|
|
110
112
|
lang
|
|
111
113
|
}, null, 4).replace(/<(\/script)/g, '<"+"$1') /*avoid breaking our script container*/}
|
|
112
114
|
document.documentElement.setAttribute('ver', HFS.VERSION.split('-')[0])
|
|
@@ -116,6 +118,9 @@ async function treatIndex(ctx, filesUri, body) {
|
|
|
116
118
|
<link rel="shortcut icon" href="/favicon.ico?${timestamp}" />
|
|
117
119
|
${(0, customHtml_1.getSection)('htmlHead')}`}
|
|
118
120
|
`;
|
|
121
|
+
function iconsToObj(icons, pre = '') {
|
|
122
|
+
return icons && (0, misc_1.objSameKeys)(icons, (v, k) => const_1.ICONS_URI + pre + k);
|
|
123
|
+
}
|
|
119
124
|
if (isBody && isOpen)
|
|
120
125
|
return `${all}
|
|
121
126
|
${isFrontend && (0, customHtml_1.getSection)('top')}
|
package/src/update.js
CHANGED
|
@@ -93,7 +93,7 @@ function localUpdateAvailable() {
|
|
|
93
93
|
}
|
|
94
94
|
exports.localUpdateAvailable = localUpdateAvailable;
|
|
95
95
|
async function updateSupported() {
|
|
96
|
-
return argv_1.argv.forceupdate || const_1.IS_BINARY && !await util_os_1.RUNNING_AS_SERVICE;
|
|
96
|
+
return process.env.DISABLE_UPDATE ? false : (argv_1.argv.forceupdate || const_1.IS_BINARY && !await util_os_1.RUNNING_AS_SERVICE);
|
|
97
97
|
}
|
|
98
98
|
exports.updateSupported = updateSupported;
|
|
99
99
|
async function update(tagOrUrl = '') {
|
package/src/upload.js
CHANGED
|
@@ -20,10 +20,21 @@ const lodash_1 = __importDefault(require("lodash"));
|
|
|
20
20
|
const events_1 = __importDefault(require("./events"));
|
|
21
21
|
const promises_1 = require("fs/promises");
|
|
22
22
|
const expiringCache_1 = require("./expiringCache");
|
|
23
|
+
const first_1 = require("./first");
|
|
23
24
|
exports.deleteUnfinishedUploadsAfter = (0, config_1.defineConfig)('delete_unfinished_uploads_after', 86400);
|
|
24
25
|
exports.minAvailableMb = (0, config_1.defineConfig)('min_available_mb', 100);
|
|
25
26
|
exports.dontOverwriteUploading = (0, config_1.defineConfig)('dont_overwrite_uploading', true);
|
|
26
27
|
const waitingToBeDeleted = {};
|
|
28
|
+
(0, first_1.onProcessExit)(() => {
|
|
29
|
+
if (!Object.keys(waitingToBeDeleted).length)
|
|
30
|
+
return;
|
|
31
|
+
console.log("removing unfinished uploads");
|
|
32
|
+
for (const path in waitingToBeDeleted)
|
|
33
|
+
try {
|
|
34
|
+
fs_1.default.rmSync(path, { force: true });
|
|
35
|
+
}
|
|
36
|
+
catch (_a) { }
|
|
37
|
+
});
|
|
27
38
|
const ATTR_UPLOADER = 'uploader';
|
|
28
39
|
function getUploadMeta(path) {
|
|
29
40
|
return (0, misc_1.loadFileAttr)(path, ATTR_UPLOADER);
|
|
@@ -148,7 +159,7 @@ function uploadWriter(base, baseUri, path, ctx) {
|
|
|
148
159
|
writeStream.once('close', async () => {
|
|
149
160
|
try {
|
|
150
161
|
await new Promise(res => fileStream.close(res)); // this only seem to be necessary on Windows
|
|
151
|
-
if (ctx.
|
|
162
|
+
if (ctx.isAborted()) {
|
|
152
163
|
if (resumableTempName && !resumableLost && !resuming) // we don't want to be left with 2 temp files
|
|
153
164
|
return (0, promises_1.rm)(tempName);
|
|
154
165
|
const sec = exports.deleteUnfinishedUploadsAfter.get();
|
package/src/util-files.js
CHANGED
|
@@ -31,7 +31,7 @@ async function readFileBusy(path) {
|
|
|
31
31
|
});
|
|
32
32
|
}
|
|
33
33
|
exports.readFileBusy = readFileBusy;
|
|
34
|
-
function watchDir(dir, cb) {
|
|
34
|
+
function watchDir(dir, cb, atStart = false) {
|
|
35
35
|
let watcher;
|
|
36
36
|
let paused = false;
|
|
37
37
|
try {
|
|
@@ -56,6 +56,8 @@ function watchDir(dir, cb) {
|
|
|
56
56
|
console.debug(String(e));
|
|
57
57
|
}
|
|
58
58
|
}
|
|
59
|
+
if (atStart)
|
|
60
|
+
controlledCb();
|
|
59
61
|
return {
|
|
60
62
|
working() { return Boolean(watcher); },
|
|
61
63
|
stop() { watcher === null || watcher === void 0 ? void 0 : watcher.close(); },
|
package/src/util-http.js
CHANGED
|
@@ -5,6 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
};
|
|
6
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
7
|
exports.httpStream = exports.httpWithBody = exports.httpString = exports.stream2string = void 0;
|
|
8
|
+
const node_url_1 = require("node:url");
|
|
8
9
|
const node_https_1 = __importDefault(require("node:https"));
|
|
9
10
|
const node_http_1 = __importDefault(require("node:http"));
|
|
10
11
|
const node_stream_1 = require("node:stream");
|
|
@@ -23,25 +24,31 @@ async function httpWithBody(url, options) {
|
|
|
23
24
|
});
|
|
24
25
|
}
|
|
25
26
|
exports.httpWithBody = httpWithBody;
|
|
26
|
-
function httpStream(url, { body, jar, noRedirect, httpThrow, ...options } = {}) {
|
|
27
|
+
function httpStream(url, { body, jar, noRedirect, httpThrow, proxy, ...options } = {}) {
|
|
27
28
|
return new Promise((resolve, reject) => {
|
|
28
|
-
var _a, _b, _c;
|
|
29
|
-
var
|
|
29
|
+
var _a, _b, _c, _d;
|
|
30
|
+
var _e, _f, _g;
|
|
31
|
+
proxy !== null && proxy !== void 0 ? proxy : (proxy = httpStream.defaultProxy);
|
|
30
32
|
(_a = options.headers) !== null && _a !== void 0 ? _a : (options.headers = {});
|
|
31
33
|
if (body) {
|
|
32
34
|
options.method || (options.method = 'POST');
|
|
33
35
|
if (lodash_1.default.isPlainObject(body)) {
|
|
34
|
-
(_b = (
|
|
36
|
+
(_b = (_e = options.headers)['content-type']) !== null && _b !== void 0 ? _b : (_e['content-type'] = 'application/json');
|
|
35
37
|
body = JSON.stringify(body);
|
|
36
38
|
}
|
|
37
39
|
if (!(body instanceof node_stream_1.Readable))
|
|
38
|
-
(_c = (
|
|
40
|
+
(_c = (_f = options.headers)['content-length']) !== null && _c !== void 0 ? _c : (_f['content-length'] = Buffer.byteLength(body));
|
|
39
41
|
}
|
|
40
42
|
if (jar)
|
|
41
43
|
options.headers.cookie = lodash_1.default.map(jar, (v, k) => `${k}=${v}; `).join('')
|
|
42
44
|
+ (options.headers.cookie || ''); // preserve parameter
|
|
43
|
-
|
|
44
|
-
|
|
45
|
+
Object.assign(options, lodash_1.default.pick((0, node_url_1.parse)(proxy || url), ['hostname', 'port', 'path', 'protocol']));
|
|
46
|
+
if (proxy) {
|
|
47
|
+
options.path = url;
|
|
48
|
+
(_d = (_g = options.headers).host) !== null && _d !== void 0 ? _d : (_g.host = (0, node_url_1.parse)(url).host || undefined);
|
|
49
|
+
}
|
|
50
|
+
const proto = options.protocol === 'https:' ? node_https_1.default : node_http_1.default;
|
|
51
|
+
const req = proto.request(options, res => {
|
|
45
52
|
var _a;
|
|
46
53
|
var _b;
|
|
47
54
|
console.debug("http responded", res.statusCode, "to", url);
|
package/src/util-os.js
CHANGED
|
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.RUNNING_AS_SERVICE = exports.runCmd = exports.getDrives = exports.getDiskSpaces = exports.getDiskSpace = exports.cmdEscape = exports.bashEscape = exports.getDiskSpaceSync = void 0;
|
|
6
|
+
exports.reg = exports.RUNNING_AS_SERVICE = exports.runCmd = exports.getDrives = exports.getDiskSpaces = exports.getDiskSpace = exports.cmdEscape = exports.bashEscape = exports.getDiskSpaceSync = void 0;
|
|
7
7
|
const path_1 = require("path");
|
|
8
8
|
const fs_1 = require("fs");
|
|
9
9
|
const child_process_1 = require("child_process");
|
|
@@ -93,3 +93,7 @@ exports.RUNNING_AS_SERVICE = const_1.IS_WINDOWS && getWindowsServicePids().then(
|
|
|
93
93
|
console.log("couldn't determine if we are running as a service");
|
|
94
94
|
console.debug(e);
|
|
95
95
|
});
|
|
96
|
+
function reg(...pars) {
|
|
97
|
+
return (0, util_1.promisify)(child_process_1.execFile)('reg', pars).then(x => x.stdout);
|
|
98
|
+
}
|
|
99
|
+
exports.reg = reg;
|
package/src/vfs.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.parentMaskApplier = exports.masksCouldGivePermission = exports.walkNode = exports.statusCodeForMissingPerm = exports.hasPermission = exports.nodeIsLink = exports.hasDefaultFile = exports.nodeIsDirectory = exports.getNodeName = exports.saveVfs = exports.vfs = exports.getNodeByName = exports.urlToNode = exports.applyParentToChild = exports.isSameFilenameAs = exports.permsFromParent = void 0;
|
|
7
|
+
exports.parentMaskApplier = exports.masksCouldGivePermission = exports.walkNode = exports.statusCodeForMissingPerm = exports.hasPermission = exports.nodeIsLink = exports.hasDefaultFile = exports.nodeIsDirectory = exports.getNodeName = exports.saveVfs = exports.vfs = exports.getNodeByName = exports.nodeStats = exports.urlToNode = exports.applyParentToChild = exports.isSameFilenameAs = exports.permsFromParent = void 0;
|
|
8
8
|
const promises_1 = __importDefault(require("fs/promises"));
|
|
9
9
|
const path_1 = require("path");
|
|
10
10
|
const misc_1 = require("./misc");
|
|
@@ -112,6 +112,7 @@ async function nodeStats(ret) {
|
|
|
112
112
|
(0, misc_1.setHidden)(ret, { stats });
|
|
113
113
|
return stats;
|
|
114
114
|
}
|
|
115
|
+
exports.nodeStats = nodeStats;
|
|
115
116
|
async function isHiddenFile(path) {
|
|
116
117
|
return const_1.IS_WINDOWS ? new Promise(res => fswin_1.default.getAttributes(path, x => res(x === null || x === void 0 ? void 0 : x.IS_HIDDEN)))
|
|
117
118
|
: path[0] === '.';
|
|
@@ -144,6 +145,14 @@ async function getNodeByName(name, parent) {
|
|
|
144
145
|
exports.getNodeByName = getNodeByName;
|
|
145
146
|
exports.vfs = {};
|
|
146
147
|
(0, config_1.defineConfig)('vfs', {}).sub(data => exports.vfs = (function recur(node) {
|
|
148
|
+
const { masks } = node;
|
|
149
|
+
lodash_1.default.each(masks, (v, mask) => {
|
|
150
|
+
if (v.maskOnly) {
|
|
151
|
+
masks[`${mask}|${v.maskOnly}|`] = v = lodash_1.default.omit(v, 'maskOnly');
|
|
152
|
+
delete masks[mask];
|
|
153
|
+
}
|
|
154
|
+
recur(v);
|
|
155
|
+
});
|
|
147
156
|
if (node.children)
|
|
148
157
|
for (const c of node.children)
|
|
149
158
|
recur(c);
|
|
@@ -273,9 +282,9 @@ async function* walkNode(parent, { ctx, depth = Infinity, prefixPath = '', requi
|
|
|
273
282
|
try {
|
|
274
283
|
let lastDir = prefixPath.slice(0, -1) || '.';
|
|
275
284
|
parentsCache.set(lastDir, parent);
|
|
276
|
-
for await (const entry of (0, misc_1.dirStream)(source, { depth, onlyFolders,
|
|
277
|
-
if (ctx === null || ctx === void 0 ? void 0 : ctx.
|
|
278
|
-
|
|
285
|
+
for await (const entry of (0, misc_1.dirStream)(source, { depth, onlyFolders, hidden: showHiddenFiles.get() })) {
|
|
286
|
+
if (ctx === null || ctx === void 0 ? void 0 : ctx.isAborted())
|
|
287
|
+
break;
|
|
279
288
|
const { path } = entry;
|
|
280
289
|
const isFolder = entry.isDirectory();
|
|
281
290
|
const name = prefixPath + (((_a = parent.rename) === null || _a === void 0 ? void 0 : _a[path]) || path);
|
|
@@ -294,7 +303,7 @@ async function* walkNode(parent, { ctx, depth = Infinity, prefixPath = '', requi
|
|
|
294
303
|
};
|
|
295
304
|
if (isFolder) // store it even if we can't see it (masks), as its children can be produced by dirStream
|
|
296
305
|
parentsCache.set(name, item);
|
|
297
|
-
if (await canSee(item))
|
|
306
|
+
if (!(onlyFiles && isFolder) && await canSee(item))
|
|
298
307
|
yield item;
|
|
299
308
|
(_b = entry.closingBranch) === null || _b === void 0 ? void 0 : _b.then(p => parentsCache.delete(p || '.'));
|
|
300
309
|
}
|
|
@@ -320,11 +329,21 @@ function masksCouldGivePermission(masks, perm) {
|
|
|
320
329
|
}
|
|
321
330
|
exports.masksCouldGivePermission = masksCouldGivePermission;
|
|
322
331
|
function parentMaskApplier(parent) {
|
|
323
|
-
const matchers = (0, misc_1.onlyTruthy)(lodash_1.default.map(parent.masks, (
|
|
332
|
+
const matchers = (0, misc_1.onlyTruthy)(lodash_1.default.map(parent.masks, (mods, k) => {
|
|
333
|
+
if (!mods)
|
|
334
|
+
return;
|
|
335
|
+
const mustBeFolder = (() => {
|
|
336
|
+
if (k.at(-1) !== '|')
|
|
337
|
+
return; // parse special flag syntax as suffix |FLAG| inside the key. This allows specifying different flags with the same mask using separate keys. To avoid syntax conflicts with the rest of the file-mask, we look for an ending pipe, as it has no practical use. Ending-pipe was preferred over starting-pipe to leave the rest of the logic (inheritMasks) untouched.
|
|
338
|
+
const i = k.lastIndexOf('|', k.length - 2);
|
|
339
|
+
if (i < 0)
|
|
340
|
+
return;
|
|
341
|
+
const type = k.slice(i + 1, -1);
|
|
342
|
+
k = k.slice(0, i); // remove
|
|
343
|
+
return type === 'folders';
|
|
344
|
+
})();
|
|
324
345
|
k = k.startsWith('**/') ? k.slice(3) : !k.includes('/') ? k : ''; // ** globstar matches also zero subfolders, so this mask must be applied here too
|
|
325
|
-
|
|
326
|
-
// k is stored into the object for debugging purposes
|
|
327
|
-
return k && { k, mods, matcher: (0, misc_1.makeMatcher)(k), mustBeFolder: maskOnly && (maskOnly === 'folders') };
|
|
346
|
+
return k && { mods, matcher: (0, misc_1.makeMatcher)(k), mustBeFolder };
|
|
328
347
|
}));
|
|
329
348
|
return async (item, virtualBasename = getNodeName(item)) => {
|
|
330
349
|
let isFolder = undefined;
|
package/src/watchLoad.js
CHANGED
|
@@ -8,16 +8,17 @@ exports.watchLoad = void 0;
|
|
|
8
8
|
const fs_1 = require("fs");
|
|
9
9
|
const promises_1 = __importDefault(require("fs/promises"));
|
|
10
10
|
const misc_1 = require("./misc");
|
|
11
|
+
const debounceAsync_1 = require("./debounceAsync");
|
|
11
12
|
const events_1 = require("./events");
|
|
12
13
|
function watchLoad(path, parser, { failedOnFirstAttempt, immediateFirst } = {}) {
|
|
13
14
|
let doing = false;
|
|
14
15
|
let watcher;
|
|
15
|
-
const debounced = (0,
|
|
16
|
+
const debounced = (0, debounceAsync_1.debounceAsync)(load, { wait: 500, maxWait: 1000 });
|
|
16
17
|
let retry;
|
|
17
18
|
let last;
|
|
18
19
|
const emitter = new events_1.BetterEventEmitter();
|
|
19
20
|
install(true);
|
|
20
|
-
const save = (0,
|
|
21
|
+
const save = (0, debounceAsync_1.debounceAsync)(async (data, { reparse = false } = {}) => {
|
|
21
22
|
await promises_1.default.writeFile(path, data, 'utf8');
|
|
22
23
|
last = data;
|
|
23
24
|
if (reparse)
|
package/src/zip.js
CHANGED
|
@@ -29,6 +29,8 @@ async function zipStreamFromFolder(node, ctx) {
|
|
|
29
29
|
const walker = !list ? (0, vfs_1.walkNode)(node, { ctx, requiredPerm: 'can_archive' })
|
|
30
30
|
: (async function* () {
|
|
31
31
|
for await (const uri of list) {
|
|
32
|
+
if (ctx.isAborted())
|
|
33
|
+
return;
|
|
32
34
|
const subNode = await (0, vfs_1.urlToNode)(uri, ctx, node);
|
|
33
35
|
if (!subNode)
|
|
34
36
|
continue;
|
|
@@ -51,7 +53,7 @@ async function zipStreamFromFolder(node, ctx) {
|
|
|
51
53
|
return; // the fact you see it doesn't mean you can get it
|
|
52
54
|
const { source } = el;
|
|
53
55
|
const name = (0, vfs_1.getNodeName)(el);
|
|
54
|
-
if (
|
|
56
|
+
if (!filter(name))
|
|
55
57
|
return;
|
|
56
58
|
try {
|
|
57
59
|
if (el.isFolder)
|
|
@@ -64,7 +66,7 @@ async function zipStreamFromFolder(node, ctx) {
|
|
|
64
66
|
return {
|
|
65
67
|
path: name,
|
|
66
68
|
size: st.size,
|
|
67
|
-
ts: st.mtime || st.
|
|
69
|
+
ts: st.mtime || st.birthtime,
|
|
68
70
|
mode: st.mode,
|
|
69
71
|
sourcePath: source,
|
|
70
72
|
getData: () => (0, fs_1.createReadStream)(source, { start: 0, end: Math.max(0, st.size - 1) })
|