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.
Files changed (44) hide show
  1. package/README.md +4 -2
  2. package/admin/assets/index-6afd4b06.js +542 -0
  3. package/{frontend/assets/sha512-70e9a0d9.js → admin/assets/sha512-f5f12dc1.js} +1 -1
  4. package/admin/index.html +1 -1
  5. package/central.json +30 -8
  6. package/frontend/assets/index-3a379840.js +94 -0
  7. package/frontend/assets/index-db5f0e4b.css +1 -0
  8. package/{admin/assets/sha512-2627feb7.js → frontend/assets/sha512-3392c31f.js} +1 -1
  9. package/frontend/index.html +2 -2
  10. package/package.json +1 -2
  11. package/plugins/download-counter/plugin.js +14 -15
  12. package/src/adminApis.js +1 -0
  13. package/src/api.auth.js +6 -6
  14. package/src/api.file_list.js +15 -8
  15. package/src/api.net.js +97 -72
  16. package/src/api.plugins.js +15 -26
  17. package/src/api.vfs.js +9 -12
  18. package/src/block.js +14 -1
  19. package/src/commands.js +1 -1
  20. package/src/comments.js +49 -0
  21. package/src/config.js +1 -5
  22. package/src/const.js +3 -2
  23. package/src/cross.js +43 -2
  24. package/src/customHtml.js +1 -1
  25. package/src/debounceAsync.js +18 -1
  26. package/src/frontEndApis.js +28 -6
  27. package/src/github.js +24 -30
  28. package/src/langs/hfs-lang-it.json +5 -3
  29. package/src/langs/hfs-lang-ru.json +6 -4
  30. package/src/listen.js +25 -15
  31. package/src/middlewares.js +3 -1
  32. package/src/misc.js +6 -5
  33. package/src/perm.js +19 -12
  34. package/src/plugins.js +62 -38
  35. package/src/serveFile.js +2 -3
  36. package/src/upload.js +5 -1
  37. package/src/util-files.js +38 -2
  38. package/src/util-http.js +8 -6
  39. package/src/vfs.js +24 -32
  40. package/src/watchLoad.js +5 -12
  41. package/src/zip.js +4 -4
  42. package/admin/assets/index-4e55b514.js +0 -536
  43. package/frontend/assets/index-cadcb0e9.css +0 -1
  44. 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 = exports.debounceAsync = void 0;
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 && (ip === '::1' || ip.endsWith('127.0.0.1'));
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: s.includes(':') ? 'ipv6' : 'ipv4' });
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.getCurrentUsernameExpanded = exports.getCurrentUsername = void 0;
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 getCurrentUsernameExpanded(ctx) {
22
- const who = getCurrentUsername(ctx);
23
- if (!who)
24
- return [];
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 === null || a === void 0 ? void 0 : a.belongs)
29
- ret.push(...a.belongs);
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.getCurrentUsernameExpanded = getCurrentUsernameExpanded;
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.Plugin = exports.pluginsMiddleware = exports.getPluginConfigFields = exports.findPluginByRepo = exports.mapPlugins = exports.getPluginInfo = exports.setPluginConfig = exports.startPlugin = exports.stopPlugin = exports.enablePlugin = exports.isPluginEnabled = exports.isPluginRunning = exports.STORAGE_FOLDER = exports.DISABLING_POSTFIX = exports.PATH = void 0;
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
- const entries = Object.entries(plugins);
159
- const sc = await serverCode.compiled();
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
- // expose public plugins' files
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
- var _a;
221
- return (_a = this.data) === null || _a === void 0 ? void 0 : _a.version;
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
- const source = await (0, promises_1.readFile)(module, 'utf8');
338
- availablePlugins[id] = parsePluginSource(id, source);
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 = plugins[id] = new Plugin(id, folder, pluginData, unwatch);
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
- const allowed = allowedReferer.get();
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, allowed))
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
- err && console.error("couldn't rename temp to", dest, String(err));
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 = __importDefault(require("fs/promises"));
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 const_1 = require("./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', () => resolve(Object.assign(res, {
16
- ok: (res.statusCode || 400) < 400,
17
- body: buf
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.statusCode === const_1.HTTP_TEMPORARY_REDIRECT && res.headers.location)
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.MIME_AUTO = exports.PERM_KEYS = exports.defaultPerms = exports.WHO_ANY_ACCOUNT = exports.WHO_NO_ONE = exports.WHO_ANYONE = void 0;
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 inheritFromParent(parent, child) {
29
- var _a, _b, _c;
30
- for (const k of (0, misc_1.typedKeys)(exports.defaultPerms)) {
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
- // // in case of object without children, parent is skipped in favor of the parent's parent
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
- (_a = child[k]) !== null && _a !== void 0 ? _a : (child[k] = inheritedPerm);
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
- (_b = child.mime) !== null && _b !== void 0 ? _b : (child.mime = parent.mime);
50
- (_c = child.accept) !== null && _c !== void 0 ? _c : (child.accept = parent.accept);
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 = exports.PERM_KEYS.length;
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 = exports.defaultPerms[perm]);
192
- if (!max-- || typeof who !== 'string' || who === exports.WHO_ANY_ACCOUNT)
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.getCurrentUsernameExpanded)(ctx))
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 === exports.WHO_ANY_ACCOUNT ? (ctx.state.account ? 0 : const_1.HTTP_UNAUTHORIZED)
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 exports.PERM_KEYS)
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
- return {
20
- unwatch: uninstall,
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 (!saving)
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 uninstall() {
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
- uninstall();
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, '', 'can_read')
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) + '/', 'can_read');
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, 'can_read', ctx))
47
- return; // the fact you see it doesn't mean you can read it
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))