hfs 0.48.3 → 0.49.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/README.md +4 -2
- package/admin/assets/index-6afd4b06.js +542 -0
- package/{frontend/assets/sha512-70e9a0d9.js → admin/assets/sha512-f5f12dc1.js} +1 -1
- package/admin/index.html +1 -1
- package/central.json +30 -8
- package/frontend/assets/index-3a379840.js +94 -0
- package/frontend/assets/index-db5f0e4b.css +1 -0
- package/{admin/assets/sha512-2627feb7.js → frontend/assets/sha512-3392c31f.js} +1 -1
- package/frontend/index.html +2 -2
- package/package.json +1 -2
- package/plugins/download-counter/plugin.js +14 -15
- package/src/adminApis.js +1 -0
- package/src/api.auth.js +6 -6
- package/src/api.file_list.js +15 -8
- package/src/api.net.js +97 -72
- package/src/api.plugins.js +15 -26
- package/src/api.vfs.js +9 -12
- package/src/block.js +14 -1
- package/src/commands.js +1 -1
- package/src/comments.js +49 -0
- package/src/config.js +1 -5
- package/src/const.js +3 -2
- package/src/cross.js +43 -2
- package/src/customHtml.js +1 -1
- package/src/debounceAsync.js +18 -1
- package/src/frontEndApis.js +28 -6
- package/src/github.js +24 -30
- package/src/langs/hfs-lang-it.json +5 -3
- package/src/langs/hfs-lang-ru.json +6 -4
- package/src/listen.js +25 -15
- package/src/middlewares.js +3 -1
- package/src/misc.js +6 -5
- package/src/perm.js +19 -12
- package/src/plugins.js +62 -38
- package/src/serveFile.js +2 -3
- package/src/upload.js +5 -1
- package/src/util-files.js +38 -2
- package/src/util-http.js +8 -6
- package/src/vfs.js +24 -32
- package/src/watchLoad.js +5 -12
- package/src/zip.js +4 -4
- package/admin/assets/index-4e55b514.js +0 -536
- package/frontend/assets/index-cadcb0e9.css +0 -1
- package/frontend/assets/index-e24651da.js +0 -94
package/src/misc.js
CHANGED
|
@@ -18,20 +18,21 @@ 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.apiAssertTypes = exports.AsapStream = exports.asyncGeneratorToReadable = exports.stream2string = exports.same = exports.matches = exports.makeMatcher = exports.makeNetMatcher = exports.isLocalHost = exports.onOff = exports.pattern2filter = exports.onFirstEvent = exports.onProcessExit =
|
|
21
|
+
exports.apiAssertTypes = exports.AsapStream = exports.asyncGeneratorToReadable = exports.stream2string = exports.same = exports.matches = exports.makeMatcher = exports.makeNetMatcher = exports.isLocalHost = exports.onOff = exports.pattern2filter = exports.onFirstEvent = exports.onProcessExit = void 0;
|
|
22
22
|
const path_1 = require("path");
|
|
23
23
|
const lodash_1 = __importDefault(require("lodash"));
|
|
24
24
|
const assert_1 = __importDefault(require("assert"));
|
|
25
25
|
__exportStar(require("./util-http"), exports);
|
|
26
26
|
__exportStar(require("./util-files"), exports);
|
|
27
27
|
__exportStar(require("./cross"), exports);
|
|
28
|
+
__exportStar(require("./debounceAsync"), exports);
|
|
28
29
|
const stream_1 = require("stream");
|
|
29
30
|
const micromatch_1 = require("micromatch");
|
|
30
31
|
const node_net_1 = require("node:net");
|
|
31
|
-
const debounceAsync_1 = __importDefault(require("./debounceAsync"));
|
|
32
|
-
exports.debounceAsync = debounceAsync_1.default;
|
|
33
32
|
const apiMiddleware_1 = require("./apiMiddleware");
|
|
34
33
|
const const_1 = require("./const");
|
|
34
|
+
const cross_1 = require("./cross");
|
|
35
|
+
const net_1 = require("net");
|
|
35
36
|
const cbs = new Set();
|
|
36
37
|
function onProcessExit(cb) {
|
|
37
38
|
cbs.add(cb);
|
|
@@ -70,7 +71,7 @@ function onOff(em, events) {
|
|
|
70
71
|
exports.onOff = onOff;
|
|
71
72
|
function isLocalHost(c) {
|
|
72
73
|
const ip = typeof c === 'string' ? c : c.socket.remoteAddress; // don't use Context.ip as it is subject to proxied ips, and that's no use for localhost detection
|
|
73
|
-
return ip && (
|
|
74
|
+
return ip && (0, cross_1.ipLocalHost)(ip);
|
|
74
75
|
}
|
|
75
76
|
exports.isLocalHost = isLocalHost;
|
|
76
77
|
function makeNetMatcher(mask, emptyMaskReturns = false) {
|
|
@@ -102,7 +103,7 @@ function makeNetMatcher(mask, emptyMaskReturns = false) {
|
|
|
102
103
|
}
|
|
103
104
|
exports.makeNetMatcher = makeNetMatcher;
|
|
104
105
|
function parseAddress(s) {
|
|
105
|
-
return new node_net_1.SocketAddress({ address: s, family:
|
|
106
|
+
return new node_net_1.SocketAddress({ address: s, family: (0, net_1.isIPv6)(s) ? 'ipv6' : 'ipv4' });
|
|
106
107
|
}
|
|
107
108
|
function makeMatcher(mask, emptyMaskReturns = false) {
|
|
108
109
|
return mask ? (0, micromatch_1.matcher)(mask.replace(/^(!)?/, '$1(') + ')') // adding () will allow us to use the pipe at root level
|
package/src/perm.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.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.
|
|
7
|
+
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.expandUsername = 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");
|
|
@@ -18,19 +18,20 @@ function getCurrentUsername(ctx) {
|
|
|
18
18
|
}
|
|
19
19
|
exports.getCurrentUsername = getCurrentUsername;
|
|
20
20
|
// provides the username and all other usernames it inherits based on the 'belongs' attribute. Useful to check permissions
|
|
21
|
-
function
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
const ret = [who];
|
|
26
|
-
for (const u of ret) {
|
|
21
|
+
function expandUsername(who) {
|
|
22
|
+
const ret = [];
|
|
23
|
+
const q = [who];
|
|
24
|
+
for (const u of q) {
|
|
27
25
|
const a = getAccount(u);
|
|
28
|
-
if (a
|
|
29
|
-
|
|
26
|
+
if (!a || a.disabled)
|
|
27
|
+
continue;
|
|
28
|
+
ret.push(u);
|
|
29
|
+
if (a.belongs)
|
|
30
|
+
q.push(...a.belongs);
|
|
30
31
|
}
|
|
31
32
|
return ret;
|
|
32
33
|
}
|
|
33
|
-
exports.
|
|
34
|
+
exports.expandUsername = expandUsername;
|
|
34
35
|
function getAccount(username, normalize = true) {
|
|
35
36
|
if (normalize)
|
|
36
37
|
username = normalizeUsername(username);
|
|
@@ -116,7 +117,7 @@ function renameAccount(from, to) {
|
|
|
116
117
|
}
|
|
117
118
|
exports.renameAccount = renameAccount;
|
|
118
119
|
// we consider all the following fields, when falsy, as equivalent to be missing. If this changes in the future, please adjust addAccount and setAccount
|
|
119
|
-
const assignableProps = ['redirect', 'ignore_limits', 'belongs', 'admin'];
|
|
120
|
+
const assignableProps = ['redirect', 'ignore_limits', 'belongs', 'admin', 'disabled'];
|
|
120
121
|
function addAccount(username, props) {
|
|
121
122
|
username = normalizeUsername(username);
|
|
122
123
|
if (!username || getAccount(username, false))
|
|
@@ -133,6 +134,8 @@ function setAccount(acc, changes) {
|
|
|
133
134
|
if (!v)
|
|
134
135
|
rest[k] = undefined;
|
|
135
136
|
Object.assign(acc, rest);
|
|
137
|
+
if (!acc.disabled)
|
|
138
|
+
delete acc.disabled;
|
|
136
139
|
if (changes.username)
|
|
137
140
|
renameAccount(acc.username, changes.username);
|
|
138
141
|
saveAccountsAsap();
|
|
@@ -167,9 +170,13 @@ function accountHasPassword(account) {
|
|
|
167
170
|
}
|
|
168
171
|
exports.accountHasPassword = accountHasPassword;
|
|
169
172
|
function accountCanLogin(account) {
|
|
170
|
-
return accountHasPassword(account);
|
|
173
|
+
return accountHasPassword(account) && !allDisabled(account);
|
|
171
174
|
}
|
|
172
175
|
exports.accountCanLogin = accountCanLogin;
|
|
176
|
+
function allDisabled(account) {
|
|
177
|
+
var _a;
|
|
178
|
+
return Boolean(account.disabled || ((_a = account.belongs) === null || _a === void 0 ? void 0 : _a.map(u => getAccount(u, false)).every(a => a && allDisabled(a))));
|
|
179
|
+
}
|
|
173
180
|
function accountCanLoginAdmin(account) {
|
|
174
181
|
return accountCanLogin(account) && Boolean(getFromAccount(account, a => a.admin));
|
|
175
182
|
}
|
package/src/plugins.js
CHANGED
|
@@ -27,7 +27,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
27
27
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
28
28
|
};
|
|
29
29
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
30
|
-
exports.parsePluginSource = exports.rescan = exports.pluginsConfig = exports.enablePlugins = exports.pluginsWatcher = exports.getAvailablePlugins = exports.
|
|
30
|
+
exports.getMissingDependencies = exports.parsePluginSource = exports.rescan = exports.pluginsConfig = exports.enablePlugins = exports.pluginsWatcher = exports.getAvailablePlugins = exports.mapPlugins = exports.Plugin = exports.pluginsMiddleware = exports.getPluginConfigFields = exports.findPluginByRepo = exports.getPluginInfo = exports.setPluginConfig = exports.startPlugin = exports.stopPlugin = exports.enablePlugin = exports.isPluginEnabled = exports.isPluginRunning = exports.STORAGE_FOLDER = exports.DISABLING_POSTFIX = exports.PATH = void 0;
|
|
31
31
|
const fast_glob_1 = __importDefault(require("fast-glob"));
|
|
32
32
|
const watchLoad_1 = require("./watchLoad");
|
|
33
33
|
const lodash_1 = __importDefault(require("lodash"));
|
|
@@ -102,17 +102,6 @@ function getPluginInfo(id) {
|
|
|
102
102
|
return running && Object.assign(running, { id }) || availablePlugins[id];
|
|
103
103
|
}
|
|
104
104
|
exports.getPluginInfo = getPluginInfo;
|
|
105
|
-
function mapPlugins(cb) {
|
|
106
|
-
return lodash_1.default.map(plugins, (pl, plName) => {
|
|
107
|
-
try {
|
|
108
|
-
return cb(pl, plName);
|
|
109
|
-
}
|
|
110
|
-
catch (e) {
|
|
111
|
-
console.log('plugin error', plName, String(e));
|
|
112
|
-
}
|
|
113
|
-
}).filter(x => x !== undefined);
|
|
114
|
-
}
|
|
115
|
-
exports.mapPlugins = mapPlugins;
|
|
116
105
|
function findPluginByRepo(repo) {
|
|
117
106
|
return lodash_1.default.find(plugins, pl => match(pl.getData()))
|
|
118
107
|
|| lodash_1.default.find(availablePlugins, match);
|
|
@@ -127,16 +116,6 @@ function getPluginConfigFields(id) {
|
|
|
127
116
|
return (_a = plugins[id]) === null || _a === void 0 ? void 0 : _a.getData().config;
|
|
128
117
|
}
|
|
129
118
|
exports.getPluginConfigFields = getPluginConfigFields;
|
|
130
|
-
const serverCode = (0, config_1.defineConfig)('server_code', '', async (script, { k }) => {
|
|
131
|
-
const res = {};
|
|
132
|
-
try {
|
|
133
|
-
new Function('exports', script)(res); // parse
|
|
134
|
-
return await initPlugin(res);
|
|
135
|
-
}
|
|
136
|
-
catch (e) {
|
|
137
|
-
return console.error(k + ':', e.message || String(e));
|
|
138
|
-
}
|
|
139
|
-
});
|
|
140
119
|
async function initPlugin(pl, more) {
|
|
141
120
|
var _a;
|
|
142
121
|
return Object.assign(pl, await ((_a = pl.init) === null || _a === void 0 ? void 0 : _a.call(pl, {
|
|
@@ -152,14 +131,10 @@ async function initPlugin(pl, more) {
|
|
|
152
131
|
})));
|
|
153
132
|
}
|
|
154
133
|
const pluginsMiddleware = async (ctx, next) => {
|
|
155
|
-
var _a;
|
|
156
134
|
const after = {};
|
|
157
135
|
// run middleware plugins
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
if (sc)
|
|
161
|
-
entries.push(['.', await serverCode.compiled()]);
|
|
162
|
-
for (const [id, pl] of entries)
|
|
136
|
+
await Promise.all(mapPlugins(async (pl, id) => {
|
|
137
|
+
var _a;
|
|
163
138
|
try {
|
|
164
139
|
const res = await ((_a = pl.middleware) === null || _a === void 0 ? void 0 : _a.call(pl, ctx));
|
|
165
140
|
if (res === true)
|
|
@@ -170,7 +145,8 @@ const pluginsMiddleware = async (ctx, next) => {
|
|
|
170
145
|
catch (e) {
|
|
171
146
|
printError(id, e);
|
|
172
147
|
}
|
|
173
|
-
|
|
148
|
+
}));
|
|
149
|
+
// expose public plugins' files`
|
|
174
150
|
if (!ctx.pluginBlockedRequest) {
|
|
175
151
|
const { path } = ctx;
|
|
176
152
|
if (path.startsWith(const_1.PLUGINS_PUB_URI)) {
|
|
@@ -215,11 +191,13 @@ class Plugin {
|
|
|
215
191
|
console.warn('invalid', k);
|
|
216
192
|
}
|
|
217
193
|
}
|
|
194
|
+
plugins[id] = this;
|
|
218
195
|
}
|
|
219
|
-
get version() {
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
}
|
|
196
|
+
get version() { var _a; return (_a = this.data) === null || _a === void 0 ? void 0 : _a.version; }
|
|
197
|
+
get description() { var _a; return (_a = this.data) === null || _a === void 0 ? void 0 : _a.description; }
|
|
198
|
+
get apiRequired() { var _a; return (_a = this.data) === null || _a === void 0 ? void 0 : _a.apiRequired; }
|
|
199
|
+
get repo() { var _a; return (_a = this.data) === null || _a === void 0 ? void 0 : _a.repo; }
|
|
200
|
+
get depend() { var _a; return (_a = this.data) === null || _a === void 0 ? void 0 : _a.depend; }
|
|
223
201
|
get middleware() {
|
|
224
202
|
var _a;
|
|
225
203
|
return (_a = this.data) === null || _a === void 0 ? void 0 : _a.middleware;
|
|
@@ -259,6 +237,33 @@ class Plugin {
|
|
|
259
237
|
}
|
|
260
238
|
}
|
|
261
239
|
exports.Plugin = Plugin;
|
|
240
|
+
const SERVER_CODE_ID = '.';
|
|
241
|
+
const serverCode = (0, config_1.defineConfig)('server_code', '', async (script, { k }) => {
|
|
242
|
+
const res = {};
|
|
243
|
+
try {
|
|
244
|
+
new Function('exports', script)(res); // parse
|
|
245
|
+
return new Plugin(SERVER_CODE_ID, '', await initPlugin(res), lodash_1.default.noop); // '.' is a name that will surely be not found among plugin folders
|
|
246
|
+
}
|
|
247
|
+
catch (e) {
|
|
248
|
+
return console.error(k + ':', e.message || String(e));
|
|
249
|
+
}
|
|
250
|
+
});
|
|
251
|
+
let serverCodePlugin;
|
|
252
|
+
serverCode.sub(() => serverCode.compiled().then(x => serverCodePlugin = x));
|
|
253
|
+
function mapPlugins(cb, includeServerCode = true) {
|
|
254
|
+
const entries = Object.entries(plugins);
|
|
255
|
+
return entries.map(([plName, pl]) => {
|
|
256
|
+
if (!includeServerCode && plName === SERVER_CODE_ID)
|
|
257
|
+
return;
|
|
258
|
+
try {
|
|
259
|
+
return cb(pl, plName);
|
|
260
|
+
}
|
|
261
|
+
catch (e) {
|
|
262
|
+
console.log('plugin error', plName, String(e));
|
|
263
|
+
}
|
|
264
|
+
}).filter(x => x !== undefined);
|
|
265
|
+
}
|
|
266
|
+
exports.mapPlugins = mapPlugins;
|
|
262
267
|
let availablePlugins = {};
|
|
263
268
|
function getAvailablePlugins() {
|
|
264
269
|
return Object.values(availablePlugins);
|
|
@@ -334,8 +339,10 @@ function watchPlugin(id, path) {
|
|
|
334
339
|
}
|
|
335
340
|
async function markItAvailable() {
|
|
336
341
|
delete plugins[id];
|
|
337
|
-
|
|
338
|
-
|
|
342
|
+
availablePlugins[id] = await parsePlugin();
|
|
343
|
+
}
|
|
344
|
+
async function parsePlugin() {
|
|
345
|
+
return parsePluginSource(id, await (0, promises_1.readFile)(module, 'utf8'));
|
|
339
346
|
}
|
|
340
347
|
async function stop() {
|
|
341
348
|
await starting;
|
|
@@ -352,6 +359,10 @@ function watchPlugin(id, path) {
|
|
|
352
359
|
return;
|
|
353
360
|
try {
|
|
354
361
|
starting = (0, misc_1.pendingPromise)();
|
|
362
|
+
// if dependencies are not ready right now, we give some time. Not super-solid but good enough for now.
|
|
363
|
+
const info = await parsePlugin();
|
|
364
|
+
if (!await (0, misc_1.waitFor)(async () => lodash_1.default.isEmpty(await getMissingDependencies(info)), { timeout: 5000 }))
|
|
365
|
+
return console.debug("plugin missing dependencies", id);
|
|
355
366
|
if (getPluginInfo(id))
|
|
356
367
|
setError(id, '');
|
|
357
368
|
const alreadyRunning = plugins[id];
|
|
@@ -392,7 +403,7 @@ function watchPlugin(id, path) {
|
|
|
392
403
|
const folder = (0, path_1.dirname)(module);
|
|
393
404
|
const { state, unwatch } = (0, customHtml_1.watchLoadCustomHtml)(folder);
|
|
394
405
|
pluginData.customHtml = state;
|
|
395
|
-
const plugin =
|
|
406
|
+
const plugin = new Plugin(id, folder, pluginData, unwatch);
|
|
396
407
|
if (alreadyRunning)
|
|
397
408
|
events_1.default.emit('pluginUpdated', Object.assign(lodash_1.default.pick(plugin, 'started'), getPluginInfo(id)));
|
|
398
409
|
else {
|
|
@@ -401,6 +412,7 @@ function watchPlugin(id, path) {
|
|
|
401
412
|
delete availablePlugins[id];
|
|
402
413
|
events_1.default.emit(wasInstalled ? 'pluginStarted' : 'pluginInstalled', plugin);
|
|
403
414
|
}
|
|
415
|
+
events_1.default.emit('pluginStarted:' + id);
|
|
404
416
|
}
|
|
405
417
|
catch (e) {
|
|
406
418
|
await markItAvailable();
|
|
@@ -414,8 +426,8 @@ function watchPlugin(id, path) {
|
|
|
414
426
|
}
|
|
415
427
|
}
|
|
416
428
|
}
|
|
417
|
-
function customApiCall(method, params) {
|
|
418
|
-
return mapPlugins(pl => { var _a, _b; return (_b = (_a = pl.getData().customApi) === null || _a === void 0 ? void 0 : _a[method]) === null || _b === void 0 ? void 0 : _b.call(_a, params); });
|
|
429
|
+
function customApiCall(method, ...params) {
|
|
430
|
+
return mapPlugins(pl => { var _a, _b; return (_b = (_a = pl.getData().customApi) === null || _a === void 0 ? void 0 : _a[method]) === null || _b === void 0 ? void 0 : _b.call(_a, ...params); });
|
|
419
431
|
}
|
|
420
432
|
function getError(id) {
|
|
421
433
|
return getPluginInfo(id).error;
|
|
@@ -470,3 +482,15 @@ function calculateBadApi(data) {
|
|
|
470
482
|
: max < const_1.COMPATIBLE_API_VERSION ? "may not work correctly as it is designed for an older version of HFS - check for updates"
|
|
471
483
|
: undefined;
|
|
472
484
|
}
|
|
485
|
+
async function getMissingDependencies(plugin) {
|
|
486
|
+
return (0, misc_1.onlyTruthy)(((plugin === null || plugin === void 0 ? void 0 : plugin.depend) || []).map((dep) => {
|
|
487
|
+
const res = findPluginByRepo(dep.repo);
|
|
488
|
+
const error = !res ? 'missing'
|
|
489
|
+
: (res.version || 0) < dep.version ? 'version'
|
|
490
|
+
: !isPluginEnabled(res.id) ? 'disabled'
|
|
491
|
+
: !isPluginRunning(res.id) ? 'stopped'
|
|
492
|
+
: '';
|
|
493
|
+
return error && { repo: dep.repo, error, id: res === null || res === void 0 ? void 0 : res.id };
|
|
494
|
+
}));
|
|
495
|
+
}
|
|
496
|
+
exports.getMissingDependencies = getMissingDependencies;
|
package/src/serveFile.js
CHANGED
|
@@ -22,11 +22,10 @@ function serveFileNode(ctx, node) {
|
|
|
22
22
|
const name = (0, vfs_1.getNodeName)(node);
|
|
23
23
|
const mimeString = typeof mime === 'string' ? mime
|
|
24
24
|
: lodash_1.default.find(mime, (val, mask) => (0, misc_1.matches)(name, mask));
|
|
25
|
-
|
|
26
|
-
if (allowed) {
|
|
25
|
+
if (allowedReferer.get()) {
|
|
27
26
|
const ref = (_a = /\/\/([^:/]+)/.exec(ctx.get('referer'))) === null || _a === void 0 ? void 0 : _a[1]; // extract host from url
|
|
28
27
|
if (ref && ref !== host() // automatic accept if referer is basically the hosting domain
|
|
29
|
-
&& !(0, misc_1.matches)(ref,
|
|
28
|
+
&& !(0, misc_1.matches)(ref, allowedReferer.get()))
|
|
30
29
|
return ctx.status = const_1.HTTP_FORBIDDEN;
|
|
31
30
|
}
|
|
32
31
|
ctx.vfsNode = node; // useful to tell service files from files shared by the user
|
package/src/upload.js
CHANGED
|
@@ -15,6 +15,7 @@ const util_os_1 = require("./util-os");
|
|
|
15
15
|
const connections_1 = require("./connections");
|
|
16
16
|
const throttler_1 = require("./throttler");
|
|
17
17
|
const perm_1 = require("./perm");
|
|
18
|
+
const comments_1 = require("./comments");
|
|
18
19
|
exports.deleteUnfinishedUploadsAfter = (0, config_1.defineConfig)('delete_unfinished_uploads_after', 86400);
|
|
19
20
|
exports.minAvailableMb = (0, config_1.defineConfig)('min_available_mb', 100);
|
|
20
21
|
const dontOverwriteUploading = (0, config_1.defineConfig)('dont_overwrite_uploading', false);
|
|
@@ -98,7 +99,10 @@ function uploadWriter(base, path, ctx) {
|
|
|
98
99
|
while (fs_1.default.existsSync(dest));
|
|
99
100
|
}
|
|
100
101
|
return fs_1.default.rename(tempName, dest, err => {
|
|
101
|
-
|
|
102
|
+
if (err)
|
|
103
|
+
console.error("couldn't rename temp to", dest, String(err));
|
|
104
|
+
else if (ctx.query.comment)
|
|
105
|
+
(0, comments_1.setCommentFor)(dest, (0, misc_1.escapeHTML)(String(ctx.query.comment)));
|
|
102
106
|
if (resumable)
|
|
103
107
|
delayedDelete(resumable, 0);
|
|
104
108
|
});
|
package/src/util-files.js
CHANGED
|
@@ -1,11 +1,34 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
// This file is part of HFS - Copyright 2021-2023, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt
|
|
3
|
+
var __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
|
+
};
|
|
3
26
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
27
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
28
|
};
|
|
6
29
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
-
exports.loadFileAttr = exports.storeFileAttr = exports.isValidFileName = exports.createFileWithPath = exports.prepareFolder = exports.unzip = exports.dirStream = exports.adjustStaticPathForGlob = exports.dirTraversal = exports.watchDir = exports.readFileBusy = exports.isDirectory = void 0;
|
|
8
|
-
const promises_1 =
|
|
30
|
+
exports.parseFile = exports.loadFileAttr = exports.storeFileAttr = exports.isValidFileName = exports.createFileWithPath = exports.prepareFolder = exports.unzip = exports.dirStream = exports.adjustStaticPathForGlob = exports.dirTraversal = exports.watchDir = exports.readFileBusy = exports.isDirectory = void 0;
|
|
31
|
+
const promises_1 = __importStar(require("fs/promises"));
|
|
9
32
|
const misc_1 = require("./misc");
|
|
10
33
|
const util_1 = require("util");
|
|
11
34
|
const fs_1 = require("fs");
|
|
@@ -176,3 +199,16 @@ async function loadFileAttr(path, k) {
|
|
|
176
199
|
return (_a = (0, misc_1.tryJson)(String(await (0, util_1.promisify)(fs_x_attributes_1.default.get)(path, FILE_ATTR_PREFIX + k)))) !== null && _a !== void 0 ? _a : undefined; // normalize, as we get null instead of undefined on windows
|
|
177
200
|
}
|
|
178
201
|
exports.loadFileAttr = loadFileAttr;
|
|
202
|
+
// read and parse a file, caching unless timestamp has changed
|
|
203
|
+
const cache = new Map();
|
|
204
|
+
async function parseFile(path, parse) {
|
|
205
|
+
const { mtime: ts } = await (0, promises_1.stat)(path);
|
|
206
|
+
const cached = cache.get(path);
|
|
207
|
+
if (ts === (cached === null || cached === void 0 ? void 0 : cached.ts))
|
|
208
|
+
return cached.parsed;
|
|
209
|
+
const raw = await (0, promises_1.readFile)(path, 'utf8');
|
|
210
|
+
const parsed = parse(raw);
|
|
211
|
+
cache.set(path, { ts, parsed });
|
|
212
|
+
return parsed;
|
|
213
|
+
}
|
|
214
|
+
exports.parseFile = parseFile;
|
package/src/util-http.js
CHANGED
|
@@ -7,15 +7,17 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
7
7
|
exports.httpStream = exports.httpString = void 0;
|
|
8
8
|
const node_http_1 = __importDefault(require("node:http"));
|
|
9
9
|
const node_https_1 = __importDefault(require("node:https"));
|
|
10
|
-
const
|
|
10
|
+
const lodash_1 = __importDefault(require("lodash"));
|
|
11
|
+
// in case the response is not 2xx, it will throw and the error object is the Response object
|
|
11
12
|
function httpString(url, options) {
|
|
12
13
|
return httpStream(url, options).then(res => new Promise(resolve => {
|
|
13
14
|
let buf = '';
|
|
14
15
|
res.on('data', chunk => buf += chunk.toString());
|
|
15
|
-
res.on('end', () =>
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
res.on('end', () => {
|
|
17
|
+
if (!lodash_1.default.inRange(res.statusCode, 200, 299))
|
|
18
|
+
throw res;
|
|
19
|
+
resolve(buf);
|
|
20
|
+
});
|
|
19
21
|
}));
|
|
20
22
|
}
|
|
21
23
|
exports.httpString = httpString;
|
|
@@ -28,7 +30,7 @@ function httpStream(url, { body, ...options } = {}) {
|
|
|
28
30
|
console.debug("http responded", res.statusCode, "to", url);
|
|
29
31
|
if (!res.statusCode || res.statusCode >= 400)
|
|
30
32
|
return reject(new Error(String(res.statusCode), { cause: res }));
|
|
31
|
-
if (res.
|
|
33
|
+
if (res.headers.location)
|
|
32
34
|
return resolve(httpStream(res.headers.location, options));
|
|
33
35
|
resolve(res);
|
|
34
36
|
}).on('error', e => {
|
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.nodeIsDirectory = exports.getNodeName = exports.saveVfs = exports.vfs = exports.urlToNode = exports.applyParentToChild = exports.isSameFilenameAs = exports.
|
|
7
|
+
exports.parentMaskApplier = exports.masksCouldGivePermission = exports.walkNode = exports.statusCodeForMissingPerm = exports.hasPermission = exports.nodeIsDirectory = exports.getNodeName = exports.saveVfs = exports.vfs = exports.urlToNode = exports.applyParentToChild = exports.isSameFilenameAs = exports.permsFromParent = exports.MIME_AUTO = 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");
|
|
@@ -13,46 +13,38 @@ const config_1 = require("./config");
|
|
|
13
13
|
const const_1 = require("./const");
|
|
14
14
|
const events_1 = __importDefault(require("./events"));
|
|
15
15
|
const perm_1 = require("./perm");
|
|
16
|
-
exports.WHO_ANYONE = true;
|
|
17
|
-
exports.WHO_NO_ONE = false;
|
|
18
|
-
exports.WHO_ANY_ACCOUNT = '*';
|
|
19
|
-
exports.defaultPerms = {
|
|
20
|
-
can_see: 'can_read',
|
|
21
|
-
can_read: exports.WHO_ANYONE,
|
|
22
|
-
can_list: 'can_read',
|
|
23
|
-
can_upload: exports.WHO_NO_ONE,
|
|
24
|
-
can_delete: exports.WHO_NO_ONE,
|
|
25
|
-
};
|
|
26
|
-
exports.PERM_KEYS = (0, misc_1.typedKeys)(exports.defaultPerms);
|
|
27
16
|
exports.MIME_AUTO = 'auto';
|
|
28
|
-
function
|
|
29
|
-
|
|
30
|
-
for (const k of
|
|
17
|
+
function permsFromParent(parent, child) {
|
|
18
|
+
const ret = {};
|
|
19
|
+
for (const k of misc_1.PERM_KEYS) {
|
|
31
20
|
let p = parent;
|
|
32
21
|
let inheritedPerm;
|
|
33
22
|
while (p) {
|
|
34
23
|
inheritedPerm = p[k];
|
|
35
|
-
//
|
|
36
|
-
if (!isWhoObject(inheritedPerm))
|
|
24
|
+
// in case of object without children, parent is skipped in favor of the parent's parent
|
|
25
|
+
if (!(0, misc_1.isWhoObject)(inheritedPerm))
|
|
37
26
|
break;
|
|
38
27
|
inheritedPerm = inheritedPerm.children;
|
|
39
28
|
if (inheritedPerm !== undefined)
|
|
40
29
|
break;
|
|
41
30
|
p = p.parent;
|
|
42
31
|
}
|
|
43
|
-
if (inheritedPerm !== undefined) // small optimization: don't expand the object
|
|
44
|
-
|
|
32
|
+
if (inheritedPerm !== undefined && child[k] === undefined) // small optimization: don't expand the object
|
|
33
|
+
ret[k] = inheritedPerm;
|
|
45
34
|
}
|
|
35
|
+
return lodash_1.default.isEmpty(ret) ? undefined : ret;
|
|
36
|
+
}
|
|
37
|
+
exports.permsFromParent = permsFromParent;
|
|
38
|
+
function inheritFromParent(parent, child) {
|
|
39
|
+
var _a, _b;
|
|
40
|
+
Object.assign(child, permsFromParent(parent, child));
|
|
46
41
|
if (typeof parent.mime === 'object' && typeof child.mime === 'object')
|
|
47
42
|
lodash_1.default.defaults(child.mime, parent.mime);
|
|
48
43
|
else
|
|
49
|
-
(
|
|
50
|
-
(
|
|
44
|
+
(_a = child.mime) !== null && _a !== void 0 ? _a : (child.mime = parent.mime);
|
|
45
|
+
(_b = child.accept) !== null && _b !== void 0 ? _b : (child.accept = parent.accept);
|
|
51
46
|
return child;
|
|
52
47
|
}
|
|
53
|
-
function isWhoObject(v) {
|
|
54
|
-
return v !== null && typeof v === 'object' && !Array.isArray(v);
|
|
55
|
-
}
|
|
56
48
|
function isSameFilenameAs(name) {
|
|
57
49
|
const lc = name.toLowerCase();
|
|
58
50
|
return (other) => lc === (typeof other === 'string' ? other : getNodeName(other)).toLowerCase();
|
|
@@ -183,25 +175,25 @@ function statusCodeForMissingPerm(node, perm, ctx, assign = true) {
|
|
|
183
175
|
return const_1.HTTP_FORBIDDEN;
|
|
184
176
|
// calculate value of permission resolving references to other permissions, avoiding infinite loop
|
|
185
177
|
let who;
|
|
186
|
-
let max =
|
|
178
|
+
let max = misc_1.PERM_KEYS.length;
|
|
187
179
|
do {
|
|
188
180
|
who = node[perm];
|
|
189
|
-
if (isWhoObject(who))
|
|
181
|
+
if ((0, misc_1.isWhoObject)(who))
|
|
190
182
|
who = who.this;
|
|
191
|
-
who !== null && who !== void 0 ? who : (who =
|
|
192
|
-
if (!max-- || typeof who !== 'string' || who ===
|
|
183
|
+
who !== null && who !== void 0 ? who : (who = misc_1.defaultPerms[perm]);
|
|
184
|
+
if (!max-- || typeof who !== 'string' || who === misc_1.WHO_ANY_ACCOUNT)
|
|
193
185
|
break;
|
|
194
186
|
perm = who;
|
|
195
187
|
} while (1);
|
|
196
188
|
if (Array.isArray(who)) {
|
|
197
189
|
const arr = who; // shut up ts
|
|
198
190
|
// check if I or any ancestor match `who`, but cache ancestors' usernames inside context state
|
|
199
|
-
const some = (0, misc_1.getOrSet)(ctx.state, 'usernames', () => (0, perm_1.
|
|
191
|
+
const some = (0, misc_1.getOrSet)(ctx.state, 'usernames', () => (0, perm_1.expandUsername)((0, perm_1.getCurrentUsername)(ctx)))
|
|
200
192
|
.some((u) => arr.includes(u));
|
|
201
193
|
return some ? 0 : const_1.HTTP_UNAUTHORIZED;
|
|
202
194
|
}
|
|
203
195
|
return typeof who === 'boolean' ? (who ? 0 : const_1.HTTP_FORBIDDEN)
|
|
204
|
-
: who ===
|
|
196
|
+
: who === misc_1.WHO_ANY_ACCOUNT ? (ctx.state.account ? 0 : const_1.HTTP_UNAUTHORIZED)
|
|
205
197
|
: (0, misc_1.throw_)(Error('invalid permission: ' + JSON.stringify(who)));
|
|
206
198
|
}
|
|
207
199
|
}
|
|
@@ -235,7 +227,7 @@ async function* walkNode(parent, ctx, depth = 0, prefixPath = '', requiredPerm)
|
|
|
235
227
|
parentsCache.set(name, item);
|
|
236
228
|
inheritMasks(item, parent, nodeName);
|
|
237
229
|
if (!ctx || hasPermission(item, 'can_list', ctx)) // check perm before recursion
|
|
238
|
-
yield* walkNode(item, ctx, depth - 1, name + '/');
|
|
230
|
+
yield* walkNode(item, ctx, depth - 1, name + '/', requiredPerm);
|
|
239
231
|
}
|
|
240
232
|
if (!source)
|
|
241
233
|
return;
|
|
@@ -338,7 +330,7 @@ events_1.default.on('accountRenamed', (from, to) => {
|
|
|
338
330
|
;
|
|
339
331
|
(function renameInNode(n) {
|
|
340
332
|
var _a;
|
|
341
|
-
for (const k of
|
|
333
|
+
for (const k of misc_1.PERM_KEYS)
|
|
342
334
|
renameInPerm(n[k]);
|
|
343
335
|
if (n.masks)
|
|
344
336
|
Object.values(n.masks).forEach(renameInNode);
|
package/src/watchLoad.js
CHANGED
|
@@ -13,21 +13,14 @@ function watchLoad(path, parser, { failedOnFirstAttempt, immediateFirst } = {})
|
|
|
13
13
|
let watcher;
|
|
14
14
|
const debounced = (0, misc_1.debounceAsync)(load, 500, { maxWait: 1000 });
|
|
15
15
|
let retry;
|
|
16
|
-
let saving;
|
|
17
16
|
let last;
|
|
18
17
|
install(true);
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
save: (data) => Promise.resolve(saving).catch(() => { }).then(() => {
|
|
22
|
-
console.debug('writing', path);
|
|
23
|
-
return saving = promises_1.default.writeFile(path, data, 'utf8').finally(() => // save but also keep track of the current operation
|
|
24
|
-
saving = undefined);
|
|
25
|
-
}) // clear
|
|
26
|
-
};
|
|
18
|
+
const save = (0, misc_1.debounceAsync)((data) => promises_1.default.writeFile(path, data, 'utf8'));
|
|
19
|
+
return { unwatch, save };
|
|
27
20
|
function install(first = false) {
|
|
28
21
|
try {
|
|
29
22
|
watcher = (0, fs_1.watch)(path, () => {
|
|
30
|
-
if (!
|
|
23
|
+
if (!save.isWorking())
|
|
31
24
|
debounced().then();
|
|
32
25
|
});
|
|
33
26
|
debounced().catch(x => x);
|
|
@@ -40,7 +33,7 @@ function watchLoad(path, parser, { failedOnFirstAttempt, immediateFirst } = {})
|
|
|
40
33
|
failedOnFirstAttempt === null || failedOnFirstAttempt === void 0 ? void 0 : failedOnFirstAttempt();
|
|
41
34
|
}
|
|
42
35
|
}
|
|
43
|
-
function
|
|
36
|
+
function unwatch() {
|
|
44
37
|
watcher === null || watcher === void 0 ? void 0 : watcher.close();
|
|
45
38
|
clearTimeout(retry);
|
|
46
39
|
watcher = undefined;
|
|
@@ -59,7 +52,7 @@ function watchLoad(path, parser, { failedOnFirstAttempt, immediateFirst } = {})
|
|
|
59
52
|
return;
|
|
60
53
|
last = text;
|
|
61
54
|
console.debug('loaded', path);
|
|
62
|
-
|
|
55
|
+
unwatch();
|
|
63
56
|
install(); // reinstall, as the original file could have been renamed. We watch by the name.
|
|
64
57
|
await parser(text);
|
|
65
58
|
}
|
package/src/zip.js
CHANGED
|
@@ -24,7 +24,7 @@ async function zipStreamFromFolder(node, ctx) {
|
|
|
24
24
|
const name = (list === null || list === void 0 ? void 0 : list.length) === 1 ? decodeURIComponent((0, path_1.basename)(list[0])) : (0, vfs_1.getNodeName)(node);
|
|
25
25
|
ctx.attachment(((0, misc_1.isWindowsDrive)(name) ? name[0] : (name || 'archive')) + '.zip');
|
|
26
26
|
const filter = (0, misc_1.pattern2filter)(String(ctx.query.search || ''));
|
|
27
|
-
const walker = !list ? (0, vfs_1.walkNode)(node, ctx, Infinity, '', '
|
|
27
|
+
const walker = !list ? (0, vfs_1.walkNode)(node, ctx, Infinity, '', 'can_archive')
|
|
28
28
|
: (async function* () {
|
|
29
29
|
for await (const uri of list) {
|
|
30
30
|
const subNode = await (0, vfs_1.urlToNode)(uri, ctx, node);
|
|
@@ -33,7 +33,7 @@ async function zipStreamFromFolder(node, ctx) {
|
|
|
33
33
|
if (await (0, vfs_1.nodeIsDirectory)(subNode)) { // a directory needs to walked
|
|
34
34
|
if ((0, vfs_1.hasPermission)(subNode, 'can_list', ctx)) {
|
|
35
35
|
yield subNode; // it could be empty
|
|
36
|
-
yield* (0, vfs_1.walkNode)(subNode, ctx, Infinity, decodeURI(uri) + '/', '
|
|
36
|
+
yield* (0, vfs_1.walkNode)(subNode, ctx, Infinity, decodeURI(uri) + '/', 'can_archive');
|
|
37
37
|
}
|
|
38
38
|
continue;
|
|
39
39
|
}
|
|
@@ -43,8 +43,8 @@ async function zipStreamFromFolder(node, ctx) {
|
|
|
43
43
|
}
|
|
44
44
|
})();
|
|
45
45
|
const mappedWalker = (0, misc_1.filterMapGenerator)(walker, async (el) => {
|
|
46
|
-
if (!(0, vfs_1.hasPermission)(el, '
|
|
47
|
-
return; // the fact you see it doesn't mean you can
|
|
46
|
+
if (!(0, vfs_1.hasPermission)(el, 'can_archive', ctx))
|
|
47
|
+
return; // the fact you see it doesn't mean you can get it
|
|
48
48
|
const { source } = el;
|
|
49
49
|
const name = (0, vfs_1.getNodeName)(el);
|
|
50
50
|
if (ctx.req.aborted || !filter(name))
|