hfs 0.43.0 → 0.44.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/README.md +50 -9
  2. package/admin/assets/index-35f6e3dc.css +1 -0
  3. package/admin/assets/index-ef68a7ab.js +510 -0
  4. package/{frontend/assets/sha512-8ebf6e2a.js → admin/assets/sha512-d091720e.js} +1 -1
  5. package/admin/index.html +2 -2
  6. package/frontend/assets/index-a3b0d6ac.js +94 -0
  7. package/frontend/assets/index-fe0f3d77.css +1 -0
  8. package/{admin/assets/sha512-55ff2fa3.js → frontend/assets/sha512-96fd938f.js} +1 -1
  9. package/frontend/index.html +3 -2
  10. package/package.json +2 -2
  11. package/plugins/download-counter/plugin.js +5 -3
  12. package/plugins/download-counter/public/main.js +2 -2
  13. package/plugins/vhosting/plugin.js +17 -11
  14. package/src/adminApis.js +1 -1
  15. package/src/api.auth.js +4 -1
  16. package/src/api.file_list.js +7 -8
  17. package/src/api.lang.js +2 -1
  18. package/src/api.vfs.js +41 -28
  19. package/src/apiMiddleware.js +3 -2
  20. package/src/const.js +4 -3
  21. package/src/customHtml.js +1 -1
  22. package/src/debounceAsync.js +3 -3
  23. package/src/frontEndApis.js +6 -6
  24. package/src/github.js +26 -8
  25. package/src/index.js +2 -1
  26. package/src/lang.js +9 -19
  27. package/src/langs/embedded.js +13 -0
  28. package/src/langs/hfs-lang-ms.json +70 -0
  29. package/src/langs/hfs-lang-ru.json +22 -22
  30. package/src/langs/hfs-lang-zh-tw.json +106 -0
  31. package/src/listen.js +1 -1
  32. package/src/log.js +6 -2
  33. package/src/middlewares.js +14 -18
  34. package/src/misc.js +7 -3
  35. package/src/plugins.js +6 -6
  36. package/src/serveFile.js +1 -1
  37. package/src/serveGuiFiles.js +2 -2
  38. package/src/update.js +1 -2
  39. package/src/upload.js +6 -6
  40. package/src/util-http.js +5 -3
  41. package/src/vfs.js +100 -52
  42. package/src/zip.js +4 -4
  43. package/admin/assets/index-5cd667a5.js +0 -511
  44. package/admin/assets/index-8ff39373.css +0 -1
  45. package/frontend/assets/index-27488fde.js +0 -94
  46. package/frontend/assets/index-54a5c76f.css +0 -1
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.masksCouldGivePermission = exports.walkNode = exports.statusCodeForMissingPerm = exports.hasPermission = exports.nodeIsDirectory = exports.getNodeName = exports.saveVfs = exports.vfs = exports.urlToNode = exports.isSameFilenameAs = exports.MIME_AUTO = 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.MIME_AUTO = exports.PERM_KEYS = exports.defaultPerms = exports.WHO_ANY_ACCOUNT = exports.WHO_NO_ONE = exports.WHO_ANYONE = 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");
@@ -17,17 +17,21 @@ exports.WHO_ANYONE = true;
17
17
  exports.WHO_NO_ONE = false;
18
18
  exports.WHO_ANY_ACCOUNT = '*';
19
19
  exports.defaultPerms = {
20
- can_see: exports.WHO_ANYONE,
20
+ can_see: 'can_read',
21
21
  can_read: exports.WHO_ANYONE,
22
- can_list: exports.WHO_ANYONE,
22
+ can_list: 'can_read',
23
23
  can_upload: exports.WHO_NO_ONE,
24
24
  can_delete: exports.WHO_NO_ONE,
25
25
  };
26
+ exports.PERM_KEYS = (0, misc_1.typedKeys)(exports.defaultPerms);
26
27
  exports.MIME_AUTO = 'auto';
27
28
  function inheritFromParent(parent, child) {
28
29
  var _a, _b, _c;
29
- for (const k of (0, misc_1.typedKeys)(exports.defaultPerms))
30
- (_a = child[k]) !== null && _a !== void 0 ? _a : (child[k] = parent[k]);
30
+ for (const k of (0, misc_1.typedKeys)(exports.defaultPerms)) {
31
+ const v = parent[k];
32
+ if (v !== undefined) // small optimization: don't expand the object
33
+ (_a = child[k]) !== null && _a !== void 0 ? _a : (child[k] = v);
34
+ }
31
35
  if (typeof parent.mime === 'object' && typeof child.mime === 'object')
32
36
  lodash_1.default.defaults(child.mime, parent.mime);
33
37
  else
@@ -40,6 +44,20 @@ function isSameFilenameAs(name) {
40
44
  return (other) => lc === (typeof other === 'string' ? other : getNodeName(other)).toLowerCase();
41
45
  }
42
46
  exports.isSameFilenameAs = isSameFilenameAs;
47
+ function applyParentToChild(child, parent, name) {
48
+ const ret = {
49
+ ...child,
50
+ original: child,
51
+ isTemp: true,
52
+ parent,
53
+ };
54
+ name || (name = child ? getNodeName(child) : '');
55
+ inheritMasks(ret, parent, name);
56
+ parentMaskApplier(parent)(ret, name);
57
+ inheritFromParent(parent, ret);
58
+ return ret;
59
+ }
60
+ exports.applyParentToChild = applyParentToChild;
43
61
  async function urlToNode(url, ctx, parent = exports.vfs, getRest) {
44
62
  var _a;
45
63
  let initialSlashes = 0;
@@ -57,19 +75,11 @@ async function urlToNode(url, ctx, parent = exports.vfs, getRest) {
57
75
  }
58
76
  // does the tree node have a child that goes by this name?
59
77
  const child = (_a = parent.children) === null || _a === void 0 ? void 0 : _a.find(isSameFilenameAs(name));
60
- const ret = {
61
- ...child,
62
- original: child,
63
- isTemp: true,
64
- };
65
- inheritMasks(ret, parent, name);
66
- applyMasks(ret, parent, name);
67
- inheritFromParent(parent, ret);
78
+ if (!child && !parent.source)
79
+ return; // on tree or on disk
80
+ const ret = applyParentToChild(child, parent, name);
68
81
  if (child) // yes
69
82
  return urlToNode(rest, ctx, ret, getRest);
70
- // not in the tree, we can see consider continuing on the disk
71
- if (!parent.source)
72
- return; // but then we need the current node to be linked to the disk, otherwise, we give up
73
83
  let onDisk = name;
74
84
  if (parent.rename) { // reverse the mapping
75
85
  for (const [from, to] of Object.entries(parent.rename))
@@ -124,15 +134,38 @@ async function nodeIsDirectory(node) {
124
134
  }
125
135
  exports.nodeIsDirectory = nodeIsDirectory;
126
136
  function hasPermission(node, perm, ctx) {
127
- var _a;
128
- return (node.source || perm !== 'can_upload') // Upload possible only if we know where to store. First check node.source because is supposedly faster.
129
- && matchWho((_a = node[perm]) !== null && _a !== void 0 ? _a : exports.defaultPerms[perm], ctx);
137
+ return !statusCodeForMissingPerm(node, perm, ctx, false);
130
138
  }
131
139
  exports.hasPermission = hasPermission;
132
- function statusCodeForMissingPerm(node, perm, ctx) {
133
- if (hasPermission(node, perm, ctx))
134
- return false;
135
- return ctx.status = node[perm] === false ? const_1.HTTP_FORBIDDEN : const_1.HTTP_UNAUTHORIZED;
140
+ function statusCodeForMissingPerm(node, perm, ctx, assign = true) {
141
+ const ret = getCode();
142
+ if (ret && assign)
143
+ ctx.status = ret;
144
+ return ret;
145
+ function getCode() {
146
+ var _a;
147
+ if (!node.source && perm === 'can_upload') // Upload possible only if we know where to store. First check node.source because is supposedly faster.
148
+ return const_1.HTTP_FORBIDDEN;
149
+ // calculate value of permission resolving references to other permissions, avoiding infinite loop
150
+ let who;
151
+ let max = exports.PERM_KEYS.length;
152
+ do {
153
+ who = (_a = node[perm]) !== null && _a !== void 0 ? _a : exports.defaultPerms[perm];
154
+ if (!max-- || typeof who !== 'string' || who === exports.WHO_ANY_ACCOUNT)
155
+ break;
156
+ perm = who;
157
+ } while (1);
158
+ if (Array.isArray(who)) {
159
+ const arr = who; // shut up ts
160
+ // check if I or any ancestor match `who`, but cache ancestors' usernames inside context state
161
+ const some = (0, misc_1.getOrSet)(ctx.state, 'usernames', () => (0, perm_1.getCurrentUsernameExpanded)(ctx))
162
+ .some((u) => arr.includes(u));
163
+ return some ? 0 : const_1.HTTP_UNAUTHORIZED;
164
+ }
165
+ return typeof who === 'boolean' ? (who ? 0 : const_1.HTTP_FORBIDDEN)
166
+ : who === exports.WHO_ANY_ACCOUNT ? (ctx.state.account ? 0 : const_1.HTTP_UNAUTHORIZED)
167
+ : (() => { throw Error('invalid permission: ' + who); })();
168
+ }
136
169
  }
137
170
  exports.statusCodeForMissingPerm = statusCodeForMissingPerm;
138
171
  // it's responsibility of the caller to verify you have list permission on parent, as callers have different needs.
@@ -141,6 +174,7 @@ async function* walkNode(parent, ctx, depth = 0, prefixPath = '', requiredPerm)
141
174
  var _a;
142
175
  const { children, source } = parent;
143
176
  const took = prefixPath ? undefined : new Set();
177
+ const maskApplier = parentMaskApplier(parent);
144
178
  if (children)
145
179
  for (const child of children) {
146
180
  const nodeName = getNodeName(child);
@@ -196,7 +230,7 @@ async function* walkNode(parent, ctx, depth = 0, prefixPath = '', requiredPerm)
196
230
  // item will be changed, so be sure to pass a temp node
197
231
  function canSee(item) {
198
232
  // we basename for depth>0 where we already have the rest of the path in the parent's url, and would be duplicated
199
- applyMasks(item, parent, (0, path_1.basename)(getNodeName(item)));
233
+ maskApplier(item, (0, path_1.basename)(getNodeName(item)));
200
234
  inheritFromParent(parent, item);
201
235
  if (ctx && !hasPermission(item, 'can_see', ctx))
202
236
  return;
@@ -209,25 +243,45 @@ function masksCouldGivePermission(masks, perm) {
209
243
  return masks !== undefined && Object.values(masks).some(props => props[perm] || masksCouldGivePermission(props.masks, perm));
210
244
  }
211
245
  exports.masksCouldGivePermission = masksCouldGivePermission;
212
- function applyMasks(item, parent, virtualBasename) {
213
- const { masks } = parent;
214
- if (!masks)
215
- return;
216
- for (const [k, v] of Object.entries(masks))
217
- if (k.startsWith('**/') && (0, misc_1.matches)(virtualBasename, k.slice(3))
218
- || !k.includes('/') && (0, misc_1.matches)(virtualBasename, k))
219
- lodash_1.default.defaults(item, v);
246
+ function parentMaskApplier(parent) {
247
+ const matchers = Object.entries(parent.masks || {}).map(([k, v]) => {
248
+ k = k.startsWith('**/') ? k.slice(3) : !k.includes('/') ? k : '';
249
+ if (!k)
250
+ return;
251
+ const m = (0, misc_1.makeMatcher)(k);
252
+ return [m, v];
253
+ });
254
+ return (item, virtualBasename) => {
255
+ if (virtualBasename === undefined)
256
+ virtualBasename = getNodeName(item);
257
+ for (const entry of matchers) {
258
+ if (!entry)
259
+ continue;
260
+ const [matcher, mods] = entry;
261
+ if (!matcher(virtualBasename))
262
+ continue;
263
+ if (item.masks)
264
+ item.masks = lodash_1.default.merge(lodash_1.default.cloneDeep(mods.masks), item.masks); // item.masks must take precedence
265
+ lodash_1.default.defaults(item, mods);
266
+ }
267
+ };
220
268
  }
269
+ exports.parentMaskApplier = parentMaskApplier;
221
270
  function inheritMasks(item, parent, virtualBasename) {
222
271
  const { masks } = parent;
223
272
  if (!masks)
224
273
  return;
225
274
  const o = {};
226
- for (const [k, v] of Object.entries(masks))
227
- if (k.startsWith('**/'))
228
- o[k.slice(3)] = v;
229
- else if (k.startsWith(virtualBasename + '/'))
230
- o[k.slice(virtualBasename.length + 1)] = v;
275
+ for (const [k, v] of Object.entries(masks)) {
276
+ const neg = k[0] === '!' && k[1] !== '(' ? '!' : '';
277
+ const withoutNeg = neg ? k.slice(1) : k;
278
+ if (withoutNeg.startsWith('**'))
279
+ o[k] = v;
280
+ else if (withoutNeg.startsWith('*/'))
281
+ o[neg + withoutNeg.slice(2)] = v;
282
+ else if (withoutNeg.startsWith(virtualBasename + '/'))
283
+ o[neg + withoutNeg.slice(virtualBasename.length + 1)] = v;
284
+ }
231
285
  if (Object.keys(o).length)
232
286
  item.masks = lodash_1.default.defaults(item.masks, o);
233
287
  }
@@ -239,24 +293,18 @@ function renameUnderPath(rename, path) {
239
293
  delete rename[''];
240
294
  return lodash_1.default.isEmpty(rename) ? undefined : rename;
241
295
  }
242
- function matchWho(who, ctx) {
243
- return who === exports.WHO_ANYONE
244
- || who === exports.WHO_ANY_ACCOUNT && Boolean(ctx.state.account)
245
- || Array.isArray(who) // check if I or any ancestor match `who`, but cache ancestors' usernames inside context state
246
- && (0, misc_1.getOrSet)(ctx.state, 'usernames', () => (0, perm_1.getCurrentUsernameExpanded)(ctx)).some((u) => who.includes(u));
247
- }
248
296
  events_1.default.on('accountRenamed', (from, to) => {
249
- recur(exports.vfs);
250
- saveVfs();
251
- function recur(n) {
297
+ ;
298
+ (function renameInNode(n) {
252
299
  var _a;
253
- for (const k of (0, misc_1.typedKeys)(exports.defaultPerms))
254
- replace(n[k]);
300
+ for (const k of exports.PERM_KEYS)
301
+ renameInPerm(n[k]);
255
302
  if (n.masks)
256
- Object.values(n.masks).forEach(recur);
257
- (_a = n.children) === null || _a === void 0 ? void 0 : _a.forEach(recur);
258
- }
259
- function replace(a) {
303
+ Object.values(n.masks).forEach(renameInNode);
304
+ (_a = n.children) === null || _a === void 0 ? void 0 : _a.forEach(renameInNode);
305
+ })(exports.vfs);
306
+ saveVfs();
307
+ function renameInPerm(a) {
260
308
  if (!Array.isArray(a))
261
309
  return;
262
310
  for (let i = 0; i < a.length; i++)
package/src/zip.js CHANGED
@@ -26,16 +26,16 @@ async function zipStreamFromFolder(node, ctx) {
26
26
  const filter = (0, misc_1.pattern2filter)(String(ctx.query.search || ''));
27
27
  const walker = !list ? (0, vfs_1.walkNode)(node, ctx, Infinity, '', 'can_read')
28
28
  : (async function* () {
29
- for await (const el of list) {
30
- const subNode = await (0, vfs_1.urlToNode)(el, ctx, node);
29
+ for await (const uri of list) {
30
+ const subNode = await (0, vfs_1.urlToNode)(uri, ctx, node);
31
31
  if (!subNode)
32
32
  continue;
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
- yield* (0, vfs_1.walkNode)(subNode, ctx, Infinity, el + '/', 'can_read');
35
+ yield* (0, vfs_1.walkNode)(subNode, ctx, Infinity, uri + '/', 'can_read');
36
36
  continue;
37
37
  }
38
- let folder = (0, path_1.dirname)(el);
38
+ let folder = (0, path_1.dirname)(decodeURIComponent(uri)); // decodeURI() won't account for %23=#
39
39
  folder = folder === '.' ? '' : folder + '/';
40
40
  yield { ...subNode, name: folder + (0, vfs_1.getNodeName)(subNode) }; // reflect relative path in archive, otherwise way may have name-clashes
41
41
  }