hfs 3.4.0-alpha1 → 3.4.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 (109) hide show
  1. package/admin/assets/AccountsPage-CoiPofEv.js +4 -0
  2. package/admin/assets/{ConfigFilePage-D5TZZyMR.js → ConfigFilePage-jUH2feqi.js} +1 -1
  3. package/admin/assets/{CustomHtmlPage-DV2-ufWZ.js → CustomHtmlPage-Pl_AJZrl.js} +1 -1
  4. package/admin/assets/{InternetPage-DT3wSQYQ.js → InternetPage-DIcAwhFk.js} +1 -1
  5. package/admin/assets/{LangPage-DbXCv7wR.js → LangPage-BwVY8QRD.js} +1 -1
  6. package/admin/assets/{LogsPage-DmAxUZpu.js → LogsPage-CcjS12cP.js} +1 -1
  7. package/admin/assets/{MonitorPage-D7ydOeq_.js → MonitorPage-DMVFXyMe.js} +1 -1
  8. package/admin/assets/OptionsPage-BB40ax9G.js +3 -0
  9. package/admin/assets/{PluginsPage-BYzfwuvg.js → PluginsPage-CC2dINcb.js} +1 -1
  10. package/admin/assets/{icons-CfBG9vvB.js → icons-C_ux1zLA.js} +1 -1
  11. package/admin/assets/{index-CVCnYMoB.js → index-BSSQOMG_.js} +4 -4
  12. package/admin/assets/{routing-C4l8rnk_.js → routing-Be72yFmd.js} +1 -1
  13. package/admin/index.html +1 -1
  14. package/frontend/assets/{dist-legacy-CKczq_fx.js → dist-legacy-D7Dg5w3L.js} +1 -1
  15. package/frontend/assets/index-legacy-CnazC3Dj.js +1 -0
  16. package/frontend/assets/{lib-legacy-D2tyILlj.js → lib-legacy-BxFHpdER.js} +1 -1
  17. package/frontend/assets/{sha512-legacy-CFcDeiiP.js → sha512-legacy-C8xoQBnP.js} +1 -1
  18. package/frontend/index.html +1 -1
  19. package/npm-shrinkwrap.json +66 -66
  20. package/package.json +1 -1
  21. package/plugins/antibrute/plugin.js +72 -18
  22. package/src/admin-langs/embedded.js +2 -2
  23. package/src/admin-langs/hfs-admin-lang-ar.json +34 -26
  24. package/src/admin-langs/hfs-admin-lang-bg.json +35 -27
  25. package/src/admin-langs/hfs-admin-lang-de.json +37 -29
  26. package/src/admin-langs/hfs-admin-lang-el.json +38 -30
  27. package/src/admin-langs/hfs-admin-lang-en.json +8 -0
  28. package/src/admin-langs/hfs-admin-lang-es.json +37 -29
  29. package/src/admin-langs/hfs-admin-lang-fi.json +100 -92
  30. package/src/admin-langs/hfs-admin-lang-fr.json +39 -31
  31. package/src/admin-langs/hfs-admin-lang-hu.json +37 -29
  32. package/src/admin-langs/hfs-admin-lang-it.json +35 -27
  33. package/src/admin-langs/hfs-admin-lang-ja.json +36 -28
  34. package/src/admin-langs/hfs-admin-lang-ko.json +39 -31
  35. package/src/admin-langs/hfs-admin-lang-lt.json +35 -27
  36. package/src/admin-langs/hfs-admin-lang-ms.json +36 -28
  37. package/src/admin-langs/hfs-admin-lang-nl.json +36 -28
  38. package/src/admin-langs/hfs-admin-lang-pl.json +36 -28
  39. package/src/admin-langs/hfs-admin-lang-pt-br.json +36 -28
  40. package/src/admin-langs/hfs-admin-lang-ro.json +37 -29
  41. package/src/admin-langs/hfs-admin-lang-ru.json +35 -27
  42. package/src/admin-langs/hfs-admin-lang-sr-latn.json +35 -27
  43. package/src/admin-langs/hfs-admin-lang-sr.json +39 -31
  44. package/src/admin-langs/hfs-admin-lang-th.json +35 -27
  45. package/src/admin-langs/hfs-admin-lang-tr.json +38 -30
  46. package/src/admin-langs/hfs-admin-lang-uk.json +35 -27
  47. package/src/admin-langs/hfs-admin-lang-vi.json +35 -27
  48. package/src/admin-langs/hfs-admin-lang-zh-hant.json +1149 -0
  49. package/src/admin-langs/hfs-admin-lang-zh.json +728 -720
  50. package/src/adminApis.js +2 -1
  51. package/src/adminLang.js +8 -7
  52. package/src/api.accounts.js +9 -4
  53. package/src/api.auth.js +11 -3
  54. package/src/api.vfs.js +3 -2
  55. package/src/auth.js +25 -14
  56. package/src/consoleLog.js +4 -0
  57. package/src/const.js +1 -1
  58. package/src/cross-const.js +1 -1
  59. package/src/cross.js +1 -1
  60. package/src/customHtml.js +3 -2
  61. package/src/deletePermission.js +120 -0
  62. package/src/events.js +6 -0
  63. package/src/index.js +3 -2
  64. package/src/lang.js +6 -1
  65. package/src/langs/embedded.js +2 -2
  66. package/src/langs/hfs-lang-ar.json +2 -1
  67. package/src/langs/hfs-lang-bg.json +2 -1
  68. package/src/langs/hfs-lang-de.json +2 -1
  69. package/src/langs/hfs-lang-el.json +2 -1
  70. package/src/langs/hfs-lang-en.json +2 -1
  71. package/src/langs/hfs-lang-es.json +2 -1
  72. package/src/langs/hfs-lang-fi.json +2 -1
  73. package/src/langs/hfs-lang-fr.json +2 -1
  74. package/src/langs/hfs-lang-hu.json +2 -1
  75. package/src/langs/hfs-lang-it.json +2 -1
  76. package/src/langs/hfs-lang-ja.json +2 -1
  77. package/src/langs/hfs-lang-ko.json +2 -1
  78. package/src/langs/hfs-lang-lt.json +2 -1
  79. package/src/langs/hfs-lang-ms.json +2 -1
  80. package/src/langs/hfs-lang-nl.json +2 -1
  81. package/src/langs/hfs-lang-pl.json +2 -1
  82. package/src/langs/hfs-lang-pt-br.json +2 -1
  83. package/src/langs/hfs-lang-ro.json +2 -1
  84. package/src/langs/hfs-lang-ru.json +2 -1
  85. package/src/langs/hfs-lang-sr-latn.json +2 -1
  86. package/src/langs/hfs-lang-sr.json +2 -1
  87. package/src/langs/hfs-lang-th.json +2 -1
  88. package/src/langs/hfs-lang-tr.json +2 -1
  89. package/src/langs/hfs-lang-uk.json +2 -1
  90. package/src/langs/hfs-lang-vi.json +2 -1
  91. package/src/langs/hfs-lang-zh-hant.json +189 -0
  92. package/src/langs/hfs-lang-zh.json +96 -98
  93. package/src/listen.js +8 -4
  94. package/src/middlewares.js +31 -28
  95. package/src/perm.js +26 -4
  96. package/src/plugins.js +2 -2
  97. package/src/serveFile.js +5 -9
  98. package/src/serveGuiAndSharedFiles.js +9 -8
  99. package/src/serveGuiFiles.js +2 -2
  100. package/src/sessionStamp.js +53 -0
  101. package/src/upload.js +1 -1
  102. package/src/uploadOwners.js +4 -2
  103. package/src/vfs.js +80 -33
  104. package/src/webdav.js +2 -2
  105. package/admin/assets/AccountsPage-C6J2hRcY.js +0 -4
  106. package/admin/assets/OptionsPage-DpN3ajRm.js +0 -3
  107. package/frontend/assets/index-legacy-ZYae30AY.js +0 -1
  108. package/src/admin-langs/hfs-admin-lang-zh-tw.json +0 -1141
  109. package/src/langs/hfs-lang-zh-tw.json +0 -187
@@ -41,10 +41,10 @@ function serveStatic(uri) {
41
41
  ctx.state.dontLog = true;
42
42
  if (ctx.method === 'OPTIONS') {
43
43
  ctx.status = const_1.HTTP_NO_CONTENT;
44
- ctx.set({ Allow: 'OPTIONS, GET' });
44
+ ctx.set({ Allow: 'OPTIONS, GET, HEAD' });
45
45
  return;
46
46
  }
47
- if (ctx.method !== 'GET')
47
+ if (!ctx.state.getOrHead)
48
48
  return ctx.status = const_1.HTTP_METHOD_NOT_ALLOWED;
49
49
  const serveApp = shouldServeApp(ctx);
50
50
  const fullPath = (0, path_1.join)(root, serveApp ? '/index.html' : ctx.path);
@@ -0,0 +1,53 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.sessionStampsReady = void 0;
4
+ exports.getSessionStamp = getSessionStamp;
5
+ exports.renewSessionStamp = renewSessionStamp;
6
+ exports.syncSessionStamps = syncSessionStamps;
7
+ const persistence_1 = require("./persistence");
8
+ const node_crypto_1 = require("node:crypto");
9
+ const stamps = new Map();
10
+ const prefix = 'sessionStamp:';
11
+ // random signing keys already invalidate all cookies on restart
12
+ const persistent = process.env.COOKIE_SIGN_KEYS !== undefined;
13
+ exports.sessionStampsReady = load();
14
+ function getSessionStamp(username) {
15
+ return stamps.get(username)?.stamp;
16
+ }
17
+ function renewSessionStamp(username) {
18
+ const current = stamps.get(username);
19
+ if (current)
20
+ setStamp(username, current.credentials);
21
+ }
22
+ function syncSessionStamps(accounts) {
23
+ for (const username of stamps.keys())
24
+ if (!(username in accounts)) {
25
+ stamps.delete(username);
26
+ if (persistent)
27
+ void persistence_1.storedMap.del(prefix + username);
28
+ }
29
+ for (const [username, account] of Object.entries(accounts)) {
30
+ // compare values across reloads and restarts without duplicating credentials in data.kv
31
+ const credentials = (0, node_crypto_1.createHash)('sha256').update(JSON.stringify([
32
+ account?.srp, account?.password, account?.plugin,
33
+ ])).digest('hex');
34
+ if (stamps.get(username)?.credentials !== credentials)
35
+ setStamp(username, credentials);
36
+ }
37
+ }
38
+ function setStamp(username, credentials) {
39
+ const stamp = `${Date.now()}.${Math.floor(Math.random() * 1000).toString().padStart(3, '0')}`;
40
+ const record = { stamp, credentials };
41
+ stamps.set(username, record);
42
+ // accepted tradeoff: with fixed signing keys, a crash before this delayed write may lose a revocation
43
+ if (persistent)
44
+ void persistence_1.storedMap.put(prefix + username, record);
45
+ }
46
+ async function load() {
47
+ for await (const [key, record] of persistence_1.storedMap.iterator({ startsWith: prefix })) {
48
+ if (persistent)
49
+ stamps.set(key.slice(prefix.length), record);
50
+ else // re-enabling the same signing keys later must not revive cookies revoked during this run
51
+ void persistence_1.storedMap.del(key);
52
+ }
53
+ }
package/src/upload.js CHANGED
@@ -72,7 +72,7 @@ function fillUploadMeta(path, ctx) {
72
72
  return saveUploadMeta(path, {
73
73
  username: (0, auth_1.getCurrentUsername)(ctx) || undefined,
74
74
  ip: ctx.ip,
75
- approved: (0, adminApis_1.ctxAdminAccess)(ctx) || undefined,
75
+ approved: Boolean((0, adminApis_1.ctxAdminAccess)(ctx)),
76
76
  });
77
77
  }
78
78
  function getUploadTempFor(fullPath) {
@@ -4,6 +4,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.uploadOwners = exports.ownUploadDeleteHours = void 0;
7
+ exports.grantUploadOwnerDelete = grantUploadOwnerDelete;
7
8
  exports.isUnfinishedUploadOwner = isUnfinishedUploadOwner;
8
9
  exports.setUploadOwner = setUploadOwner;
9
10
  exports.moveUploadOwner = moveUploadOwner;
@@ -33,7 +34,8 @@ setInterval(() => {
33
34
  void exports.uploadOwners.del(k);
34
35
  }
35
36
  }, misc_1.MINUTE);
36
- events_1.default.on('checkVfsPermission', ({ node, perm, ctx }) => {
37
+ events_1.default.on('checkVfsPermission', grantUploadOwnerDelete);
38
+ function grantUploadOwnerDelete({ node, perm, ctx }) {
37
39
  if (perm !== 'can_delete' || !node.source)
38
40
  return;
39
41
  const { vfsPath } = node;
@@ -49,7 +51,7 @@ events_1.default.on('checkVfsPermission', ({ node, perm, ctx }) => {
49
51
  }
50
52
  if (matchesOwner(owner, ctx))
51
53
  return 0;
52
- });
54
+ }
53
55
  function matchesOwner(owner, ctx) {
54
56
  const username = (0, auth_1.getCurrentUsername)(ctx);
55
57
  const { sessionId } = owner;
package/src/vfs.js CHANGED
@@ -27,6 +27,7 @@ exports.simpleWhoToError = simpleWhoToError;
27
27
  exports.walkNode = walkNode;
28
28
  exports.masksCouldGivePermission = masksCouldGivePermission;
29
29
  exports.parentMaskApplier = parentMaskApplier;
30
+ exports.renameUnderPath = renameUnderPath;
30
31
  const promises_1 = __importDefault(require("fs/promises"));
31
32
  const path_1 = require("path");
32
33
  const misc_1 = require("./misc");
@@ -81,6 +82,7 @@ function inheritFromParent(child) {
81
82
  child.accept ??= parent.accept;
82
83
  if (parent.default)
83
84
  child.default ??= parent.default;
85
+ child.gui_asset ??= parent.gui_asset;
84
86
  return child;
85
87
  }
86
88
  function isSameFilenameAs(name) {
@@ -94,6 +96,11 @@ async function isSameFilePath(a, b) {
94
96
  if (normalizeFilename((0, path_1.resolve)(a)) === normalizeFilename((0, path_1.resolve)(b)))
95
97
  return true;
96
98
  try {
99
+ if (const_1.IS_WINDOWS) { // pkg's realpath preserves 8.3 names; expanding them here also preserves junction identity
100
+ const [longA, longB] = await Promise.all([convertWindowsPath(a, true), convertWindowsPath(b, true)]);
101
+ if (longA && longB && normalizeFilename((0, path_1.resolve)(longA)) === normalizeFilename((0, path_1.resolve)(longB)))
102
+ return true;
103
+ }
97
104
  const stats = await Promise.all([promises_1.default.lstat(a), promises_1.default.lstat(b)]);
98
105
  if (stats.some(x => x.isSymbolicLink()))
99
106
  return false;
@@ -194,6 +201,9 @@ async function getNodeByName(name, parent, assumeMissingToBeFolder = false) {
194
201
  const sourceName = Object.entries(parent.rename || {}).find(([, to]) => sameName(to))?.[0] || name;
195
202
  if (!child) {
196
203
  child = findSourceChild();
204
+ // a shared alias may publish comment storage only through its explicit VFS name
205
+ if (child?.source && isCommentsFileName((0, path_1.basename)(child.source)))
206
+ return;
197
207
  if (!child && const_1.IS_WINDOWS && parent.source) {
198
208
  const aliases = await windowsPathNames((0, path_1.join)(parent.source, sourceName), parent.source);
199
209
  if (!aliases)
@@ -248,6 +258,8 @@ async function getNodeByName(name, parent, assumeMissingToBeFolder = false) {
248
258
  ret.source = (0, path_1.join)(parent.source, onDisk);
249
259
  ret.original = undefined; // this will overwrite the 'original' set in applyParentToChild, so we know this is not part of the vfs
250
260
  await setIsFolder(ret);
261
+ if (ret.isFolder === false && isCommentsFileName(onDisk))
262
+ return; // implicit comment storage contains metadata for files the requester may not be allowed to see
251
263
  if (assumeMissingToBeFolder)
252
264
  ret.isFolder ??= true;
253
265
  return ret;
@@ -269,6 +281,9 @@ async function getNodeByName(name, parent, assumeMissingToBeFolder = false) {
269
281
  return parent.children?.find(x => getSourceNames(x, parent).some(source => normalized.has(normalizeFilename(source))));
270
282
  }
271
283
  }
284
+ function isCommentsFileName(name) {
285
+ return (0, comments_1.usingDescriptIon)() && [comments_1.DESCRIPT_ION, comments_1.DESCRIPT_ION_ALT].some(isSameFilenameAs(name));
286
+ }
272
287
  const smartUncFolderDetection = (0, config_1.defineConfig)(misc_1.CFG.smart_unc_folder_detection, false);
273
288
  async function setIsFolder(node) {
274
289
  if (!node.source)
@@ -402,7 +417,7 @@ function nodeIsLink(node) {
402
417
  function hasPermission(node, perm, ctx) {
403
418
  return !statusCodeForMissingPerm(node, perm, ctx, false);
404
419
  }
405
- function statusCodeForMissingPerm(node, perm, ctx, assign = true) {
420
+ function statusCodeForMissingPerm(node, perm, ctx, assign = true, checkListeners = true) {
406
421
  const ret = getCode();
407
422
  if (ret && assign) {
408
423
  ctx.status = ret;
@@ -432,7 +447,7 @@ function statusCodeForMissingPerm(node, perm, ctx, assign = true) {
432
447
  } while (1);
433
448
  if ((0, misc_1.isWhoObject)(who) || isWhoVfsPerms(who))
434
449
  throw Error(`permission type-guard: ${JSON.stringify(who)}`);
435
- const first = lodash_1.default.max(events_1.default.emit('checkVfsPermission', { who, node, perm, ctx }));
450
+ const first = checkListeners ? lodash_1.default.max(events_1.default.emit('checkVfsPermission', { who, node, perm, ctx })) : undefined;
436
451
  if (first !== undefined)
437
452
  return first;
438
453
  return simpleWhoToError(who, ctx)
@@ -462,7 +477,6 @@ async function* walkNode(parent, { ctx, depth = Infinity, prefixPath = '', requi
462
477
  const { source } = parent;
463
478
  const taken = new Set();
464
479
  const takenSources = new Set();
465
- const maskApplier = parentMaskApplier(parent);
466
480
  const visitLater = [];
467
481
  const childrenWorking = parent.children?.length && Promise.all(parent.children.map(async (child) => {
468
482
  if (ctx?.isAborted())
@@ -473,7 +487,7 @@ async function* walkNode(parent, { ctx, depth = Infinity, prefixPath = '', requi
473
487
  for (const sourceName of getSourceNames(child, parent))
474
488
  takenSources.add(normalizeFilename(prefixPath + sourceName));
475
489
  const item = setVfsPath({ ...child, original: child, name, parent }, name, parent);
476
- if (await cantSee(item))
490
+ if (await cantSee(item, parent, nodeName))
477
491
  return;
478
492
  if (item.source && !item.children?.length && !item.see_without_probing) // real items must be accessible, unless probing was explicitly disabled
479
493
  try {
@@ -483,11 +497,10 @@ async function* walkNode(parent, { ctx, depth = Infinity, prefixPath = '', requi
483
497
  return;
484
498
  }
485
499
  const isFolder = nodeIsFolder(child);
486
- if (onlyFiles ? !isFolder : (!onlyFolders || isFolder))
500
+ if (canEmit(item, isFolder))
487
501
  stream.push(item);
488
502
  if (!depth || !isFolder || cantRecur(item))
489
503
  return;
490
- inheritMasks(item, parent);
491
504
  visitLater.push([item, name]); // prioritize siblings
492
505
  }));
493
506
  try {
@@ -497,31 +510,52 @@ async function* walkNode(parent, { ctx, depth = Infinity, prefixPath = '', requi
497
510
  && !hasPermission(parent, requiredPerm, ctx)
498
511
  && !masksCouldGivePermission(parent.masks, requiredPerm))
499
512
  return;
500
- const pathMaskApplier = parentMaskApplier(parent, true);
513
+ const diskFolders = new Map();
514
+ diskFolders.set('', { node: parent, virtualPath: '' });
515
+ const applyPathMask = parentMaskApplier(parent, true, true);
516
+ const applyRootNameMask = parentMaskApplier(parent, false, true);
501
517
  try {
502
518
  await (0, walkDir_1.walkDir)(source, { depth, ctx, hidden: showHiddenFiles.get(), parallelizeRecursion }, async (entry) => {
503
519
  if (ctx?.isAborted())
504
520
  return null;
505
- if ((0, comments_1.usingDescriptIon)() && (entry.name === comments_1.DESCRIPT_ION || entry.name === comments_1.DESCRIPT_ION_ALT))
506
- return;
507
521
  const { path } = entry; // this path is not the original deprecated property: we are overwriting/reusing it
508
522
  const isFolder = entry.isDirectory();
509
- let renamed = parent.rename?.[path];
510
- if (renamed) {
511
- const dir = (0, path_1.dirname)(path); // if `path` isn't just the name, copy its dir in renamed
512
- if (dir !== '.')
513
- renamed = dir + '/' + renamed;
514
- }
515
- const name = prefixPath + (renamed || path);
523
+ if (!isFolder && isCommentsFileName(entry.name))
524
+ return;
525
+ const parentPath = (0, path_1.dirname)(path) === '.' ? '' : (0, path_1.dirname)(path);
526
+ const evaluatedParent = diskFolders.get(parentPath);
527
+ if (!evaluatedParent)
528
+ return false;
529
+ const physicalName = (0, path_1.basename)(path);
530
+ const virtualName = evaluatedParent.node.rename?.[physicalName] || physicalName;
531
+ const virtualPath = evaluatedParent.virtualPath
532
+ ? evaluatedParent.virtualPath + '/' + virtualName
533
+ : virtualName;
534
+ const name = prefixPath + virtualPath;
516
535
  if (taken.has(normalizeFilename(name))
517
536
  || takenSources.has(normalizeFilename(prefixPath + path))) // taken by vfs node above
518
537
  return false; // false just in case it's a folder
519
- const item = setVfsPath({ name, isFolder, source: (0, path_1.join)(source, path), parent, stats: entry.stats }, name, parent);
520
- // masks containing '/' must be matched against the relative path while keeping walkDir recursion enabled
521
- await pathMaskApplier(item, renamed || path);
522
- if (await cantSee(item)) // can't see: don't produce and don't recur
538
+ const item = setVfsPath({
539
+ name: virtualName,
540
+ isFolder,
541
+ source: (0, path_1.join)(source, path),
542
+ parent: evaluatedParent.node,
543
+ rename: renameUnderPath(evaluatedParent.node.rename, physicalName),
544
+ stats: entry.stats,
545
+ }, virtualName, evaluatedParent.node);
546
+ const rootMaskProperties = { isFolder };
547
+ const exceptions = applyPathMask(rootMaskProperties, virtualPath);
548
+ for (const key of applyRootNameMask(rootMaskProperties, virtualName))
549
+ exceptions.add(key);
550
+ for (const key of exceptions)
551
+ if (!(key in rootMaskProperties))
552
+ rootMaskProperties[key] = parent[key];
553
+ if (await cantSee(item, evaluatedParent.node, virtualName, rootMaskProperties)) // can't see: don't produce and don't recur
523
554
  return false;
524
- if (onlyFiles ? !isFolder : (!onlyFolders || isFolder))
555
+ if (isFolder)
556
+ diskFolders.set(path, { node: item, virtualPath });
557
+ item.name = name;
558
+ if (canEmit(item, isFolder))
525
559
  stream.push(item);
526
560
  if (cantRecur(item))
527
561
  return false;
@@ -544,10 +578,16 @@ async function* walkNode(parent, { ctx, depth = Infinity, prefixPath = '', requi
544
578
  function cantRecur(item) {
545
579
  return ctx && !hasPermission(item, 'can_list', ctx);
546
580
  }
581
+ function canEmit(item, isFolder) {
582
+ return !(requiredPerm && ctx && !hasPermission(item, requiredPerm, ctx))
583
+ && (onlyFiles ? !isFolder : (!onlyFolders || isFolder));
584
+ }
547
585
  // item will be changed, so be sure to pass a temp node
548
- async function cantSee(item) {
549
- await maskApplier(item);
586
+ async function cantSee(item, itemParent, virtualName, pathPermissions) {
587
+ inheritMasks(item, itemParent, virtualName);
588
+ await parentMaskApplier(itemParent)(item, virtualName);
550
589
  inheritFromParent(item);
590
+ Object.assign(item, pathPermissions);
551
591
  if (ctx && !hasPermission(item, 'can_see', ctx))
552
592
  return true;
553
593
  item.isTemp = true;
@@ -564,7 +604,7 @@ async function* walkNode(parent, { ctx, depth = Infinity, prefixPath = '', requi
564
604
  function masksCouldGivePermission(masks, perm) {
565
605
  return masks !== undefined && Object.values(masks).some(props => props[perm] || masksCouldGivePermission(props.masks, perm));
566
606
  }
567
- function parentMaskApplier(parent, pathBased = false) {
607
+ function parentMaskApplier(parent, pathBased = false, trackExceptions = false) {
568
608
  // rules are met in the parent.masks object from nearest to farthest, but since we finally apply with _.defaults, the nearest has precedence in the final result
569
609
  const matchers = (0, misc_1.onlyTruthy)(lodash_1.default.map(parent.masks, (mods, mask) => {
570
610
  if (!mods)
@@ -582,11 +622,6 @@ function parentMaskApplier(parent, pathBased = false) {
582
622
  if (pathBased) {
583
623
  if (!mask.includes('/'))
584
624
  return;
585
- // avoid evaluating twice masks like **/*.png because parentMaskApplier already handles them by basename
586
- const m = /^(!?)\*\*\//.exec(mask);
587
- // this keeps the fast basename path as source-of-truth for patterns that collapse to a filename after **/
588
- if (m && !mask.slice(m[0].length).includes('/'))
589
- return;
590
625
  }
591
626
  else {
592
627
  const m = /^(!?)\*\*\//.exec(mask); // ** globstar matches also zero subfolders, so this mask must be applied here too
@@ -594,22 +629,34 @@ function parentMaskApplier(parent, pathBased = false) {
594
629
  if (!mask)
595
630
  return;
596
631
  }
597
- return mask && { matcher: (0, misc_1.makeMatcher)(mask), mods, mustBeFolder };
632
+ return mask && {
633
+ matcher: (0, misc_1.makeMatcher)(mask),
634
+ exceptionMatcher: trackExceptions && mask.startsWith('!') ? (0, misc_1.makeMatcher)(mask.slice(1)) : undefined,
635
+ mods,
636
+ mustBeFolder,
637
+ };
598
638
  }));
599
639
  return (item, virtualName = (pathBased ? lodash_1.default.identity : path_1.basename)(getNodeName(item))) => {
600
640
  // depth traversal passes full relative paths, while node traversal still matches only basenames
641
+ const exceptions = new Set();
601
642
  let isFolder = undefined;
602
- for (const { matcher, mods, mustBeFolder } of matchers) {
643
+ for (const { matcher, exceptionMatcher, mods, mustBeFolder } of matchers) {
603
644
  if (mustBeFolder !== undefined) {
604
645
  isFolder ??= nodeIsFolder(item);
605
646
  if (mustBeFolder !== isFolder)
606
647
  continue;
607
648
  }
608
- if (!matcher(virtualName))
649
+ if (!matcher(virtualName)) {
650
+ if (exceptionMatcher?.(virtualName))
651
+ for (const key of misc_1.PERM_KEYS)
652
+ if (mods[key] !== undefined && !(key in item))
653
+ exceptions.add(key);
609
654
  continue;
655
+ }
610
656
  item.masks &&= lodash_1.default.merge(lodash_1.default.cloneDeep(mods.masks), item.masks); // item.masks must take precedence
611
657
  lodash_1.default.defaults(item, mods);
612
658
  }
659
+ return exceptions;
613
660
  };
614
661
  }
615
662
  // propagates masks, don't apply
@@ -619,7 +666,7 @@ function inheritMasks(item, parent, virtualBasename = getNodeName(item)) {
619
666
  return;
620
667
  const o = {};
621
668
  for (const [k, v] of Object.entries(masks)) {
622
- if (k.startsWith('**')) {
669
+ if (/^!?\*\*/.test(k)) {
623
670
  o[k] = v;
624
671
  continue;
625
672
  }
package/src/webdav.js CHANGED
@@ -133,7 +133,7 @@ const webdav = async (ctx, next) => {
133
133
  const isCorsPreflight = ctx.method === 'OPTIONS' && ctx.get('Access-Control-Request-Method');
134
134
  const isKnownWebdavAgent = KNOWN_UA.test(ua) || webdavDetectedAgents.has(webdavAgentKey(ctx, ua));
135
135
  const isWebdavAuthRequest = !isCorsPreflight && (ctx.method === 'OPTIONS' || WEBDAV_METHODS.has(ctx.method) || WEBDAV_HINT_HEADERS.some(h => ctx.get(h))
136
- || ctx.method === 'GET' && isKnownWebdavAgent);
136
+ || ctx.state.getOrHead && isKnownWebdavAgent);
137
137
  if (isWebdavAuthRequest)
138
138
  ctx.state.webdavDetected = true;
139
139
  if (isWebdavAuthRequest && ua && (0, auth_1.getCurrentUsername)(ctx))
@@ -147,7 +147,7 @@ const webdav = async (ctx, next) => {
147
147
  return;
148
148
  if (ctx.method === 'OPTIONS')
149
149
  return handleOptions();
150
- if (ctx.method === 'GET' && isWebdavAuthRequest)
150
+ if (ctx.state.getOrHead && isWebdavAuthRequest)
151
151
  return handleGet();
152
152
  switch (ctx.method) {
153
153
  case 'PUT': return handlePut();
@@ -1,4 +0,0 @@
1
- import{i as e}from"./rolldown-runtime-CacB07dV.js";import{B as t,Bt as n,C as r,Dt as i,E as a,En as o,Et as s,F as c,G as l,J as u,Kt as d,L as f,Nt as p,O as m,Ot as h,Qt as g,Rt as _,St as v,U as y,Ut as b,V as x,X as ee,Y as S,Yt as C,Z as w,Zt as T,_ as E,_r as D,_t as O,an as k,ar as A,at as te,b as ne,bn as j,bt as M,cn as N,d as re,dt as P,et as ie,fr as F,ft as I,gn as L,gr as R,h as z,hr as B,ht as ae,ir as oe,it as V,jn as se,k as ce,kn as le,lr as H,lt as ue,mn as de,ot as fe,p as pe,qt as me,ur as U,v as he,vr as W,vt as G,xt as ge,y as _e,yr as ve,z as K,zt as ye}from"./index-CVCnYMoB.js";import{_ as be,g as xe,h as Se,m as Ce,v as we}from"./icons-CfBG9vvB.js";var q=e(ve());U();var Te=[`password`,`password2`,`adminActualAccess`,`hasPassword`,`invalidated`,`canLogin`,`members`];function Ee({account:e,done:n,groups:c,addToBar:l,reload:u}){var d,p;let{username:m}=I(),[h,_]=(0,q.useState)(e),[b,x]=(0,q.useState)([]),ee=le();(0,q.useEffect)(()=>{var t;_(e),x(c.filter(t=>t!==e.username)),ee||(t=E.current)==null||(t=t.querySelector(`input`))==null||t.focus()},[JSON.stringify(e)]);let w=!e.username,{isGroup:T}=h,E=(0,q.useRef)(),{members:D}=e,A=(d=e.plugin)==null?void 0:d.auth;return(0,q.createElement)(O,{key:e.username,formRef:E,values:h,set(e,t){_(n=>H(H({},n),{},{[t]:e}))},barSx:H({gap:2,width:`100%`},ue()),stickyBar:!0,addToBar:[!w&&(0,q.createElement)(t,H(H({icon:a,title:k`Delete`,confirm:k(`Delete {username}?`,{username:e.username})},m===e.username&&{disabled:!0,title:k`Cannot delete current account`}),{},{onClick:()=>F(`del_account`,{username:e.username}).then(u)})),(0,q.createElement)(t,{icon:Ce,title:k`Invalidate past sessions`+(e.invalidated?`
2
- `+k(`(already invalidated sessions before {timestamp})`,{timestamp:s(e.invalidated)}):``),confirm:k(`Invalidate all sessions up to now for "{username}"?`,{username:e.username}),doneMessage:!0,onClick:()=>F(`invalidate_sessions`,{username:e.username}).then(u)}),...oe(l)],fields:[{k:`username`,label:k(T?`Group name`:`Username`),autoComplete:`off`,required:!0,md:T&&!A?12:4,helperText:k`Case-insensitive`,getError:t=>t!==e.username&&F(`get_account`,{username:t}).then(t=>(t==null?void 0:t.username)===e.username?k`usernames are case-insensitive`:k`already used`,()=>!1)},A&&{k:``,md:8,comp:(0,q.createElement)(g,{severity:`info`},k`Authentication handled by a plugin`)},!T&&!A&&{k:`password`,xs:6,md:4,type:`password`,autoComplete:`new-password`,required:w,label:w?k`Password`:k`Change password`},!T&&!A&&{k:`password2`,xs:6,md:4,type:`password`,autoComplete:`new-password`,label:k`Repeat password`,getError:(e,{values:t})=>(e||``)!==(t.password||``)&&k`Enter same password`},{k:`disabled`,comp:G,fromField:e=>!e,toField:e=>!e,label:k`Enabled`,xs:12,sm:6,lg:4,helperText:h.disabled||h.canLogin!==!1?k`login_disabled_by_account_or_groups`:(0,q.createElement)(C,{sx:{color:`warning.main`},component:`span`},new Date(e.expire)<new Date?k`Login is prevented because account is expired`:k`login_disabled_by_groups`)},{k:`ignore_limits`,label:k`Ignore limits`,comp:G,xs:12,sm:6,lg:4,helperText:h.ignore_limits?k`Speed limits don't apply to this account`:k`Speed limits apply to this account`},H({k:`admin`,comp:G,fromField:e=>e||null,label:k`Admin-panel access`,xs:12,sm:6,lg:4,helperText:k`To access THIS interface you are using right now`},!e.admin&&e.adminActualAccess&&{value:!0,disabled:!0,helperText:k`inherited_permission_notice`}),!T&&{k:`require_password_change`,label:k`Require password change`,comp:G,xs:12,sm:6,lg:4,helperText:k`At next login, but can be dismissed`},{k:`disable_password_change`,label:k`Password change`,comp:v,xs:12,sm:6,lg:4,defaultValue:null,options:{[k(`default_setting`,{defaultValue:k(h.canChangePassword?`Allowed`:`Disabled`)})]:null,[k`Allowed`]:!1,[k`Disabled`]:!0}},D?T&&!D.length?(0,q.createElement)(C,{},k`No members`):D.length>0&&(0,q.createElement)(K,{gap:0,flexWrap:`wrap`},k(`members_count`,{n:D.length}),o(`,\xA0`,(p=e.members)==null?void 0:p.map(e=>(0,q.createElement)(c.includes(e)?`i`:`span`,{},e))),(0,q.createElement)(f,{icon:a,confirm:k(`delete_items`,{n:e.members.length}),onClick:()=>F(`del_account`,{username:e.members}).then(u),sx:{verticalAlign:`text-top`}})):null,T&&(0,q.createElement)(g,{severity:`info`},k`group_add_members_hint`),{k:`belongs`,comp:ge,label:k`Inherit from groups`,options:b,sm:6,lg:4,helperText:[k`Specify groups to inherit permissions from.`,T&&k`A group can inherit from another group.`,!b.length&&k`no_groups_available`].filter(Boolean).join(` `)},{k:`allow_net`,comp:y,label:k`Allowed network address`,sm:6,lg:4,placeholder:k`any address`},!T&&{k:`auto_login_net`,comp:y,label:k`Auto-login by IP address`,sm:6,lg:4,placeholder:k`none`},{k:`redirect`,label:k`Redirect`,comp:re,placeholder:k`no`,sm:6,lg:4,helperText:k`account_redirect_hint`},{k:`expire`,label:k`Expiration`,sm:6,lg:4,comp:r,toField:e=>e&&new Date(e),helperText:k`When expired, login won't be allowed`},H(H({k:`days_to_live`,label:k`Days to live`,sm:6,lg:4,comp:M,step:`any`,min:1/1e3},h.expire&&{xs:12,disabled:!0,sx:{opacity:.2}}),{},{helperText:k`Used to set expiration on first login`}),{k:`notes`,label:k`Notes`,multiline:!0,sm:6,lg:4}],onError:V,save:H(H({children:k`Save`},S(i(h,e))),{},{onClick(){return W(function*(){var t;let{password:r=``,password2:i,adminActualAccess:a,hasPassword:o,invalidated:s,canLogin:c,members:l}=h,u=se(h,Te),d=((t=E.current)==null?void 0:t.querySelector(`button.saveBtn`))||void 0;if(w){let e=yield F(`add_account`,u);if(r)try{yield N(h.username,r)}catch(e){throw F(`del_account`,{username:h.username}),e}n(e==null?void 0:e.username,d);return}let f=yield F(`set_account`,{username:e.username,changes:u});r&&(yield N(h.username,r),_(e=>H(H({},e),{},{password:``,password2:``}))),e.username===m&&(P.username=f.username),n(f==null?void 0:f.username,d)})()}})})}var J=e(D());R(),U();var Y={fieldSeparator:`,`,lineSeparator:`
3
- `,quote:`"`},X={field:0,fieldOffset:0,line:0,lineOffset:0,quoted:!1,appendCell:!1,appendField:!1,appendRow:!1},De=class extends Error{constructor(e,t){super(`Invalid CSV at ${e}:${t}`)}},Oe=class{constructor(e=Y){B(this,`_rows`,[]),B(this,`_row`,[]),B(this,`_cell`,``),B(this,`_options`,Y),B(this,`_state`,H({},X)),B(this,`_index`,0),B(this,`_current`,``),B(this,`_previous`,``),B(this,`_quoteState`,H({},X)),this._options=Object.assign({},Y,e)}parse(e){for(this.reset(),this._index=0;this._index<e.length;this._index++)this._state.appendCell=!0,this._previous=this._current,this._current=e[this._index],this.handleNext();if(this._row.length>0&&(this.addField(this.fieldValue(this._cell),this._row,this._state),this.addRow(this._row,this._rows,this._state)),this._state.quoted)throw new De(this._quoteState.line,this._quoteState.lineOffset);return this.makeImmutable(),this._rows}get rows(){return this._rows}get json(){if(this.rows.length>0){let e=this.rows[0].filter(e=>typeof e==`string`);return Object.freeze(this.rows.filter((e,t)=>e&&t>0).map(t=>{let n={};return e.forEach((e,r)=>{n[e]=t[r]}),Object.freeze(n)}))}return Object.freeze([])}handleNext(){this.handleQuote()||this.handleFieldSeparator()||this.handleLineSeparator(),this.processState()}handleQuote(){return this._current===this._options.quote?(this._quoteState=H({},this._state),this._previous===`\\`?this.handleQuoteEscaped():this.handleQuoteNotEscaped(),!0):!1}handleQuoteEscaped(){this._cell=this._cell.slice(0,Math.max(0,this._cell.length-1))}handleQuoteNotEscaped(){if(this._cell.length===0||this._state.quoted)this._state.quoted=!this._state.quoted;else throw new De(this._state.line,this._state.lineOffset);this._state.appendCell=!1}handleFieldSeparator(){return this._current===this._options.fieldSeparator?(this._state.quoted||(this._state.appendCell=!1,this._state.appendField=!0),!0):!1}handleLineSeparator(){return this._current===this._options.lineSeparator?(this._state.quoted||(this._state.appendCell=!1,this._state.appendField=!0,this._state.appendRow=!0),!0):!1}processState(){this._state.appendCell&&(this._cell+=this._current),this._state.appendField&&(this.addField(this.fieldValue(this._cell),this._row,this._state),this._cell=``),this._state.appendRow&&(this.addRow(this._row,this._rows,this._state),this._row=[]),this._state.lineOffset++,this._state.fieldOffset++}fieldValue(e){return e}addField(e,t,n){t.push(e),n.field++,n.fieldOffset=-1,n.appendField=!1}addRow(e,t,n){t.push(e),n.field=0,n.line++,n.lineOffset=-1,n.appendRow=!1}makeImmutable(){this.rows.forEach(e=>{e.forEach(e=>Object.freeze(e)),Object.freeze(e)}),Object.freeze(this._rows)}reset(){this._rows=[],this._row=[],this._cell=``,this._state=H({},X),this._index=0,this._current=``,this._previous=``,this._quoteState=H({},X)}};U();function ke(e){return Z.apply(this,arguments)}function Z(){return Z=W(function*(e){L(function(){var t=W(function*(t){let n=t==null?void 0:t[0];if(!n)return;let r=yield de(n);if(!r)return;let i=new Oe().parse(r.trim()),a={comp:M,min:1,max:9,xs:6,typing:!0},o={skipFirstLines:0,usernameColumn:1,passwordColumn:2,groupColumn:3,redirectColumn:4,overwriteExistingAccounts:!1},s=yield fe({title:k`Import accounts from CSV`,dialogProps:{maxWidth:`sm`},values:o,form:e=>{let t=i[e.skipFirstLines||0]||[],n=l(t,H(H({},o),e));return{save:{startIcon:(0,q.createElement)(z),children:k`Go`},fields:[(0,q.createElement)(C,{sx:{p:1}},k`Total lines:`,i.length),{k:`skipFirstLines`,label:k`Skip First Lines`,comp:M,max:i.length-1,typing:!0,md:6,getError:e=>e!=null&&!Number.isInteger(e)&&`Enter an integer`,helperText:(0,q.createElement)(q.Fragment,{},k`First line: `,(0,q.createElement)(`code`,{},t.join(`, `)))},{k:`overwriteExistingAccounts`,label:k`Overwrite Existing Accounts`,comp:G,md:6},H(H({k:`usernameColumn`,label:k`Username Column`},a),{},{helperText:(0,q.createElement)(q.Fragment,{},k`First username: `,n.u)}),H(H({k:`passwordColumn`,label:k`Password Column`},a),{},{helperText:(0,q.createElement)(q.Fragment,{},k`First password: `,n.p)}),H(H({k:`groupColumn`,label:k`Group Column`},a),{},{helperText:(0,q.createElement)(q.Fragment,{},k`First group: `,n.g)}),H(H({k:`redirectColumn`,label:k`Redirect Column`},a),{},{helperText:(0,q.createElement)(q.Fragment,{},k`First redirect: `,n.r)})]}}});if(!s)return;let{close:c}=j({title:k`Importing...`,Content(){let[t,n]=(0,q.useState)(0),[r,a]=(0,q.useState)();return(0,q.useEffect)(()=>{let t=!1;return setTimeout(W(function*(){if(t)return;let r=0,o=0,u=s.skipFirstLines,d=i.length-u,f=0;try{for(let e of i){var p;if(t)return;if(u){u--;continue}let i=l(e,s);a(i),n(f++/d),yield F(`add_account`,{username:i.u,belongs:(p=i.g)==null?void 0:p.split(`,`),redirect:i.r,overwrite:s.overwriteExistingAccounts}).then(()=>{if(i.p)return N(i.u,i.p)}).catch(e=>{if(e.code===409)return o++;r++})}}finally{c();let t=f-r-o;V([k`Results:`,k(`account_import_failed`,{n:r}),k(`account_import_succeeded`,{n:t}),k(`account_import_skipped_existing`,{n:o})].join(`
4
- `),!t&&r?`error`:r||o?`warning`:`success`),e==null||e()}})),()=>{t=!0}},[]),(0,q.createElement)(C,{sx:{display:`flex`,gap:2,alignItems:`center`}},(0,q.createElement)(x,{icon:m,progress:t}),r==null?void 0:r.u)}});function l(e,t){return{u:e[t.usernameColumn-1],p:e[t.passwordColumn-1],g:e[t.groupColumn-1],r:e[t.redirectColumn-1]}}});return function(e){return t.apply(this,arguments)}}(),{multiple:!1,accept:`.csv`})}),Z.apply(this,arguments)}U();var Ae=` `,Q=e=>e==null?void 0:e.split(Ae).at(-1);function je(){let{username:e,accountsAsTree:r}=I(),{data:i,reload:o,element:s}=c(`get_accounts`),[m,v]=(0,q.useState)([]),y=Array.isArray(m);(0,q.useEffect)(()=>{Array.isArray(i==null?void 0:i.list)&&y&&v(m.filter(e=>i.list.find(t=>(t==null?void 0:t.username)===Q(e))))},[i]);let x=(0,q.useMemo)(()=>i&&J.default.sortBy(i.list,[e=>!e.isGroup,e=>!e.adminActualAccess,`username`]),[i]),S=y&&J.default.find(x,{username:Q(m[0])}),D=w(`md`),O=!(m.length>0)||!x?null:y&&m.length>1?(0,q.createElement)(q.Fragment,{},(0,q.createElement)(K,{},(0,q.createElement)(T,{variant:`h6`},k(`select_count`,{n:m.length})),(0,q.createElement)(f,{onClick:z,icon:a},k`Remove`)),(0,q.createElement)(n,{},J.default.uniq(m.map(Q)).map(e=>(0,q.createElement)(ye,{key:e},(0,q.createElement)(_,{},e))))):A(S||L(),e=>(0,q.createElement)(Ee,{account:e,groups:x.filter(e=>e.isGroup).map(e=>e.username),addToBar:D&&[(0,q.createElement)(C,{sx:{flex:1}}),$(e,{fontSize:`large`,sx:{p:1}}),(0,q.createElement)(t,{icon:ae,title:k`Close`,onClick:R})],reload:o,done(e,t){v(D?[e]:[]),o(),l(``,t)}}));(0,q.useEffect)(()=>{if(D||!O||!m.length)return;let{close:e}=j({title:J.default.isString(m)?J.default.startCase(m):m.length>1?k`Multiple selection`:S?k(S.isGroup?`Group: `:`User: `)+S.username:`?`,Content:()=>O,onClose(e){e||R()}});return()=>void e(!0)},[D,m,S]);let M={height:`100%`,display:`flex`,flexDirection:`column`,overflow:`auto`},[N,re]=ie(k`Show tree`,k`Show list`,()=>({icon:p}),r);return P.accountsAsTree=N,s||(0,q.createElement)(b,{container:!0,sx:{rowSpacing:1,columnSpacing:2,top:0,flex:`1 1 auto`,height:0}},(0,q.createElement)(b,{size:{xs:12,md:5,lg:4,xl:5},sx:M},(0,q.createElement)(C,{sx:{display:`flex`,flexWrap:`wrap`,gap:2,mb:2,boxShadow:e=>`0px -8px 4px 10px ${e.palette.background.paper}`,position:`sticky`,top:0,zIndex:2,backgroundColor:`background.paper`,width:`fit-content`}},(0,q.createElement)(pe,{variant:`contained`,startIcon:(0,q.createElement)(xe),items:[{children:k`user`,onClick:()=>v(`new-user`)},{children:k`group`,onClick:()=>v(`new-group`)},{children:k`from CSV`,onClick:()=>ke(o)}]},k`Add`),ee(o),re,(x==null?void 0:x.length)>0&&(0,q.createElement)(T,{sx:{p:1}},k(`accounts_count`,{n:x.length}))),!(x!=null&&x.length)&&(0,q.createElement)(g,{severity:`info`},h(k`remote_admin_account_required`)),(0,q.createElement)(ne,{multiSelect:!0,sx:{pr:4,pb:2,minWidth:`15em`},selectedItems:y?m:[],slots:{collapseIcon:E,expandIcon:he},onSelectedItemsChange(e,t){var n,r;!(e==null||(n=e.target)==null||(r=n.closest)==null)&&r.call(n,`.MuiTreeItem-iconContainer`)||v(t)}},x&&(function e(t,n=``){return t.map(t=>{var r,i;return(0,q.createElement)(_e,{key:t.username,itemId:n+t.username,label:(0,q.createElement)(C,{sx:{display:`flex`,flexWrap:`wrap`,padding:`.2em 0`,columnGap:`.5em`,alignItems:`center`}},$(t),(t.disabled||t.canLogin===!1)&&u(we,t.disabled?k`Disabled`:k`Disabled by its groups`,t.disabled?void 0:{color:`text.secondary`}),(t.expire||t.days_to_live)&&(0,q.createElement)(Se),t.adminActualAccess&&u(ce,k`Can login into Admin`),t.username,!!((r=t.belongs)!=null&&r.length)&&(0,q.createElement)(C,{sx:{color:`text.secondary`,fontSize:`small`}},`(`,(i=t.belongs)==null?void 0:i.join(`, `),`)`))},N&&e(x.filter(e=>{var n;return(n=t.directMembers)==null?void 0:n.includes(e.username)}),n+t.username+Ae))})})(N?x.filter(e=>!x.some(t=>{var n;return(n=t.members)==null?void 0:n.includes(e.username)})):x))),D&&O&&(0,q.createElement)(b,{size:`grow`,sx:H(H({},M),{},{maxWidth:`100%`})},(0,q.createElement)(me,{sx:{overflow:`initial`}},(0,q.createElement)(d,{},O))));function L(){return{username:``,hasPassword:m===`new-user`,adminActualAccess:!1,invalidated:void 0,canLogin:!0,canChangePassword:!0,isGroup:m===`new-group`,members:[],directMembers:[]}}function R(){v([])}function z(){return B.apply(this,arguments)}function B(){return B=W(function*(){if(typeof m==`string`||m.some(t=>Q(t)===e)&&!(yield te(k`current_account_delete_warning`)))return;let t=J.default.without(J.default.uniq(m.map(Q)),e);if(!t.length)return V(k`Nothing to delete`,`info`);if(!(yield te(k(`delete_items`,{n:t.length}))))return;let n=[];for(let e of t)(yield F(`del_account`,{username:e}).then(()=>1,()=>0))||n.push(e);if(o(),n.length)return V(k(`items_delete_failed`,{n:n.length,list:n.join(`, `)}),`error`)}),B.apply(this,arguments)}}function $(e,t={}){return(0,q.createElement)(e.isGroup?m:be,t)}export{je as default};
@@ -1,3 +0,0 @@
1
- import{i as e}from"./rolldown-runtime-CacB07dV.js";import{Cn as t,Ct as n,F as r,Fn as i,Ft as a,G as o,H as s,J as c,Jt as l,M as u,N as d,Nn as f,On as p,Ot as m,Pn as h,Pt as g,Qn as _,S as v,Sn as y,St as b,Tt as ee,U as x,Vn as te,W as S,Wt as C,Y as ne,Yt as w,Z as re,_n as ie,_r as ae,_t as oe,a as T,an as E,ar as D,at as O,bt as k,ct as se,dt as A,fr as ce,ft as le,it as j,j as ue,l as de,lr as M,nn as fe,or as N,pt as pe,s as me,sn as he,t as ge,tr as _e,tt as P,u as F,ur as I,vr as ve,vt as L,xn as ye,yr as R}from"./index-CVCnYMoB.js";import{t as be}from"./icons-CfBG9vvB.js";import{t as xe}from"./routing-C4l8rnk_.js";var z=e(R()),B=e(ae()),V=`//---`;function Se(e){let t=e.split(RegExp(`(?:^|
2
- )`+B.default.escapeRegExp(V),`g`)),n=[],r=!0;for(let e of t)if(r){r=!1;let t=e.trim();t&&n.push({name:``,code:t})}else{let[t,r]=_(`
3
- `,e);n.push({name:t,code:r})}return n}var H=F();I();var U,Ce=[`net`,`frontend`,`uploads`,`others`],we=y({changes:{}});t(A,e=>{e.some(e=>e[1][0]===`config`)&&Te()});function W(){var e,t,n;let{data:_,reload:y,element:C}=r(`get_config`,{omit:[`vfs`]}),ae=le(),{changes:F}=ye(we),I=r(_&&`get_status`),R=I.data,W=I.reload,ke=()=>d(e=>{U&&Object.assign(U,e),setTimeout(W,1e3),setTimeout(W,2e3)});(0,z.useEffect)(()=>void W(),[_]);let Ae=re(`sm`),je=(0,z.useRef)(null),Me=p(),[Ne,Pe]=xe(`options`,Ce),G={},K=(e=r(`get_admins`).data)==null?void 0:e.list,q=R==null?void 0:R.connectionAddress,J=window.location.hostname===`localhost`,Fe=q==null?void 0:q.includes(`:`),Ie=[{label:E`any`,value:``,disabled:!1},{label:E`any IPv4`,value:`0.0.0.0`,disabled:!J&&(!q||Fe)},{label:E`any IPv6`,value:`::`,disabled:!J&&(!q||!Fe)},...[`127.0.0.1`,`::1`].map(e=>({label:e,value:e,disabled:!J&&q!==e})),...(R==null||(t=R.ips)==null?void 0:t.map(e=>({value:e,disabled:q!==e})))||[]];if(C)return C;if(I.error)return I.element;let Y=U===_?ae.config:A.config=U=_,Le={comp:k,min:1,unit:`KB/s`,placeholder:E`no limit`,sm:6},X={comp:k,placeholder:E`no limit`,toField:e=>e||``,sm:4},Z=Y[N.https_port]>=0;return(0,z.createElement)(oe,{sx:{maxWidth:`60em`},values:Y,set(e,t){A.config[t]=e},stickyBar:!0,onError:j,onValidation(e,t){if(!t)return;let n=e&&Object.keys(e).find(t=>e[t]);n&&G[n]!==void 0&&Pe(G[n])},save:M({children:E`Save`,ref:je,onClick:Q},ne(Object.keys(F).length>0)),barSx:{gap:2},addToBar:[(0,z.createElement)(l,{onClick(){y(),W()},startIcon:(0,z.createElement)(ee)},E`Reload`),(0,z.createElement)(l,{component:fe,href:`/config`,startIcon:(0,z.createElement)(be)},Ae?E`Config file`:E`File`)],defaults(){return{xs:6}},fields:Re({Net:[{k:N.port,comp:Ee,xs:12,sm:4,label:E`HTTP port`,status:(R==null?void 0:R.http)||!0,suggestedPort:80},{k:N.https_port,comp:Ee,xs:12,sm:4,label:E`HTTPS port`,status:(R==null?void 0:R.https)||!0,suggestedPort:443,onChange(e){return e>=0&&!Z&&!Y[N.cert]&&ke(),e}},{k:N.upnp_enabled,comp:L,xs:12,sm:4,label:E`UPnP/SSDP`,helperText:E`Port forwarding and double-NAT detection`},Z&&{k:N.cert,comp:T,sm:4,label:E`HTTPS certificate file`,helperText:P(`HTTPS#certificate`,E`What is this?`),error:D(R==null?void 0:R.https.error,e=>ue(e)&&(R.https.listening?e:[e,` - `,(0,z.createElement)(s,{key:`fix`,onClick:ke},E`make one`)]))},Z&&M({k:N.private_key,comp:T,sm:4,label:E`HTTPS private key file`},D(R==null?void 0:R.https.error,e=>u(e)?{error:!0,helperText:e}:null)),Z&&{k:N.force_https,comp:L,label:E`Force HTTPS`,sm:4,disabled:!Z||Y[N.port]<0,helperText:E`Not applied to localhost. Doesn't work with proxies.`},{k:N.listen_interface,comp:b,label:E`Listen interface`,sm:4,afterList:Ie.some(e=>e.disabled)&&(0,z.createElement)(w,{sx:{p:`8px 16px 0`,borderTop:`1px solid`,fontSize:`small`}},E`If an address is disabled, connect through it to select it`),options:Ie},M(M({k:N.max_kbps},Le),{},{sm:4,label:E`Limit output`,helperText:E`Doesn't apply to localhost`}),M(M({k:N.max_kbps_per_ip},Le),{},{sm:4,label:E`Limit output per-IP`}),M(M({k:N.max_downloads},X),{},{label:E`Max downloads`,helperText:E`Number of simultaneous downloads`}),M(M({k:N.max_downloads_per_ip},X),{},{label:E`Max downloads per-IP`}),M(M({k:N.max_downloads_per_account},X),{},{label:E`Max downloads per-account`,helperText:E`Overrides other limits`}),{k:N.admin_net,comp:x,xs:12,sm:6,label:E`Admin-panel accessible from`,placeholder:E`any address`,helperText:E`admin_net_localhost_hint`},{k:N.localhost_admin,comp:L,xs:12,sm:6,label:E`Consider localhost access as Admin`,getError:e=>!e&&(K==null?void 0:K.length)===0&&E`First create at least one admin account`,helperText:E`Access admin-panel without entering credentials`},{k:N.proxies,comp:k,xs:6,md:3,max:9,label:E`Number of incoming HTTP proxies`,placeholder:E`none`,error:ge(Y,R),helperText:E`Necessary to detect users' IP`},{k:N.outbound_interface,comp:b,xs:6,md:3,label:E`Outbound interface`,options:[{label:E`automatic`,value:``},...(R==null||(n=R.ips)==null?void 0:n.map(e=>({value:e})))||[]]},{k:N.outbound_proxy,xs:6,md:4,label:E`Outbound proxy`,placeholder:E`none`,helperText:E`URL form`,getError:e=>_e(()=>e&&new URL(e)&&``,()=>`Invalid URL`)},{k:N.allowed_referer,comp:De,md:2,placeholder:E`any`,label:E`Links from other websites`,helperText:E`In case another website is linking your files`},{k:N.block,label:!1,comp:v,xs:12,prepend:!0,sm:!0,autoRowHeight:!0,form:{sx:{maxWidth:`40em`}},fields:[{k:`ip`,label:E`Blocked IP`,sm:12,required:!0,wrap:!0,$width:2,comp:x,$column:{mergeRender:{comment:{},expire:{}}},helperText:E`block_self_warning`},{k:`expire`,label:E`Expire`,$type:`dateTime`,minDate:new Date,sm:6,$hideUnder:`sm`,helperText:E`Leave empty for no expiration`},{k:`disabled`,$type:`boolean`,label:E`Enabled`,helperText:E`geo_rule_disable_hint`,toField:e=>!e,fromField:e=>e?void 0:!0,sm:6,$width:80},{k:`comment`,label:E`Comment`,$hideUnder:`sm`}]}],"Front-end":[{k:N.file_menu_on_link,comp:b,label:E`Access file menu`,md:3,options:{[E`by clicking on file name`]:!0,[E`by dedicated button`]:!1}},{k:N.menu_at_top,comp:b,md:3,label:E`Menu panel`,options:{[E`at top`]:!0,[E`at bottom`]:!1}},{k:N.title,md:6,helperText:E`You can see this in the tab of your browser`},{k:N.auto_play_seconds,comp:k,xs:6,sm:3,min:1,max:1e4,required:!0,label:E`Auto-play seconds delay`,helperText:m(E(`show_interface_default_help`,{docsUrl:f+`discussions/270`}))},{k:N.tile_size,comp:k,xs:6,sm:3,max:10,required:!0,label:E`Default tiles size`,helperText:P(`Tiles`,E`To enable tiles-mode`)},{k:N.theme,comp:b,xs:6,sm:3,options:i},{k:N.sort_by,comp:b,xs:6,sm:3,options:h},{k:N.invert_order,comp:L,xs:6,md:3},{k:N.folders_first,comp:L,xs:6,md:3},{k:N.sort_numerics,comp:L,xs:6,md:3,label:E`Sort numeric names`},{k:N.title_with_path,comp:L,xs:6,md:3},{k:N.favicon,comp:T,placeholder:E`None`,fileMask:`*.ico|`+he,xs:12,sm:6,helperText:E`The icon associated to your website`},{k:N.show_uploader,label:E`Show uploader to`,comp:me,xs:!0},{k:N.page_size,comp:k,xs:!0,min:1,required:!0,helperText:E`Entries per page`}],Uploads:[{k:N.dont_overwrite_uploading,comp:L,md:4,label:E`Uploads don't overwrite`,helperText:E`Files are automatically numbered (frontend only)`},{k:N.split_uploads,comp:k,unit:`MB`,md:2,step:.1,min:.1,fromField:e=>e*1e6,toField:e=>e?e/1e6:null,placeholder:E`disabled`,label:E`Split uploads in chunks`,helperText:E`Overcome proxy limits (frontend only)`},{k:N.delete_unfinished_uploads_after,comp:k,md:3,min:0,unit:E`seconds`,required:!0,label:E`Delete unfinished uploads after`},{k:N.min_available_mb,comp:k,md:3,min:0,unit:`MBytes`,placeholder:E`None`,label:E`Min. available disk space`,helperText:E`Reject uploads that don't comply`},{k:N.own_upload_delete_hours,comp:k,xs:12,sm:6,md:3,min:0,unit:E`hours`,required:!0,label:E`Uploaders can delete their files for`,helperText:E`Set to 0 to disable`}],Others:[{k:N.show_hidden_files,comp:L,sm:3,label:E`Include hidden files`},{k:N.descript_ion_encoding,sm:3,label:E`Encoding of file DESCRIPT.ION`,comp:b,disabled:Y[N.comments_storage]===`attr`,options:[`utf8`,720,775,819,850,852,862,869,874,808,...B.default.range(1250,1257),10029,20866,21866]},{k:N.comments_storage,comp:b,xs:12,sm:6,options:{[E`in file DESCRIPT.ION`]:``,[E`in file attributes`]:`attr`,[E`in file attributes + load DESCRIPT.ION`]:`attr+ion`}},{k:N.keep_session_alive,comp:L,sm:6,md:6,helperText:E`frontend_page_open_session_hint`},{k:N.session_duration,comp:k,sm:3,md:3,min:5,unit:`seconds`,required:!0},{k:N.size_1024,label:E`KB size`,comp:b,sm:3,options:{1e3:!1,1024:!0}},{k:N.open_browser_at_start,comp:L,label:E`Open Admin-panel at start`,xs:12,sm:6,md:3,helperText:E`Browser is automatically launched with HFS`},{k:N.zip_calculate_size_for_seconds,comp:k,xs:12,sm:6,md:3,unit:`seconds`,required:!0,label:E`Calculate ZIP size for`,helperText:E`download_progress_time_hint`},{k:N.mime,comp:v,label:E`Custom MIME types`,reorder:!0,prepend:!0,xs:12,sm:12,md:6,fields:[{k:`v`,label:E`Mime type`,placeholder:E`auto`,$width:2,helperText:E`Leave empty to get automatic value`},{k:`k`,label:E`File mask`,helperText:(0,z.createElement)(S),$width:1,$column:{renderCell:({value:e,id:t})=>(0,z.createElement)(`code`,{},e,e===`*`&&t<B.default.size(Y[N.mime])-1&&c(pe,m(E`mime_order_hint`),{color:`warning.main`,ml:1}))}}],toField:e=>Object.entries(e||{}).map(([e,t])=>({k:e,v:t})),fromField:e=>Object.fromEntries(e.map(e=>[e.k,e.v||`auto`])),helperText:E`Most MIME types are detected automatically`},{k:N.force_webdav_login,comp:Oe,sm:!0,label:E`WebDAV force login`,fallbackRE:`Microsoft-WebDAV`,helperText:[E`force_login_compat_help`,P(`webdav`,E`Why?`)]},Y[N.force_webdav_login]!==!0&&{k:N.webdav_initial_auth,comp:Oe,sm:6,label:E`WebDAV initial auth`,helperText:E`force_login_once_help`},{k:N.server_code,comp:v,xs:12,fields:[{k:`name`,label:E`Name`,$width:.4,$render:({value:e})=>e.trim()||(0,z.createElement)(`i`,{},E`no name`)},{k:`code`,label:E`Code`,comp:de,lang:`js`,$hideUnder:`sm`,$render:({value:e})=>typeof e==`string`?(0,z.createElement)(ie,{},(0,H.highlight)(e,H.languages.js,`js`)):`?`,helperText:m(E(`server_code_plugin_help`,{docsUrl:f+`blob/main/dev-plugins.md`}))}],toField:e=>!e||typeof e!=`string`?[]:Se(e),fromField:ze,applyButton:{onClick:e=>Q({[N.server_code]:ze(e)})}}]})});function Re(e){let t=Object.entries(e),n=[];for(let[e,[,r]]of t.entries())for(let t of r){if(!t){n.push(t);continue}G[t.k]=e,n.push(M(M({},t),{},{parentProps:M(M({},t.parentProps),{},{hidden:e!==Ne})}))}return[{k:``,xs:12,comp:(0,z.createElement)(g,{value:Ne,variant:`scrollable`,scrollButtons:`auto`,onChange(e,t){Pe(t)}},t.map(([e])=>(0,z.createElement)(a,{key:e,label:E(e),sx:{minWidth:0}}))),parentProps:{ref:Me,sx:{position:`sticky`,top:{xs:56,sm:64,lg:0},zIndex:2,bgcolor:`background.paper`}}},...n]}function ze(e){return e.map(({name:e=``,code:t=``},n)=>n||e?`${V}${e}\n${t}\n`:t).join(``)}function Q(){return $.apply(this,arguments)}function $(){return $=ve(function*(e={}){var t,n;let r=M(M({},F),e);if(B.default.isEmpty(r))return se(E`Nothing to save`);let i=window.location,a=[N.port,N.https_port];if(a.every(e=>r[e]!==void 0))return j(E`change_both_ports_warning`,`warning`);let s=[R==null||(t=R.http)==null?void 0:t.listening,R==null||(n=R.https)==null?void 0:n.listening],c=location.protocol===`https:`;c&&(a.reverse(),s.reverse());let l=r[a[0]],u=Y[a[1]],d=u>0&&s[1],f=c?`http`:`https`;if(l<0&&!d)return j(E(`cannot_disable_only_fixed_port`,{otherProtocol:f}),`warning`);if(l===0&&!d)return j(E(`cannot_randomize_only_fixed_port`,{otherProtocol:f}),`warning`);let p=l>0&&l!=i.port;if(p&&!(yield O(E`You are changing the port and you may be disconnected`)))return;let m=N.cert in r||N.private_key in r;if(c&&m&&!(yield O(E`You may disrupt https service, kicking you out`)))return;yield ce(`set_config`,{values:r}),N.split_uploads in r&&(yield j(E`split_uploads_reload_notice`,`warning`));let h=te(i.hostname),g=i.pathname+i.hash,v=l<=0?`${c?`http:`:`https:`}//${h}:${u}${g}`:p?`${i.protocol}//${h}:${l||Y[a[0]]}${g}`:yield D(`https://${h}:${i.port}${g}`,e=>{var t;return!c&&((t=r[N.force_https])==null?_[N.force_https]:t)&&fetch(e).then(()=>e,()=>0)});if(v)return yield j(E`redirect_warning`,`warning`),window.location.href=v;let y=N.port in r||N.https_port in r;setTimeout(W,y||m?1e3:0),Object.assign(U,r),Te(),o(!1,je.current)}),$.apply(this,arguments)}}function Te(){let e={};if(A.config)for(let[t,n]of Object.entries(A.config))JSON.stringify(n)!==JSON.stringify(U==null?void 0:U[t])&&(e[t]=n);we.changes=e}function Ee({label:e,value:t,onChange:n,setApi:r,status:i,suggestedPort:a=1,error:o,helperText:s}){var c;let l=(0,z.useRef)(a);t>0&&(l.current=t);let d=Number(t>0?l.current:t)||0,f=i==null?void 0:i.error;return f&&(ue(f)||u(f)?f=void 0:o=!0),(0,z.createElement)(w,{},(0,z.createElement)(w,{sx:{display:`flex`}},(0,z.createElement)(b,{sx:{flexGrow:1},label:e,error:o,value:d,options:[{label:E`off`,value:-1},{label:E`random`,value:0},{label:E`choose`,value:l.current}],onChange:n}),t>0&&(0,z.createElement)(k,{label:E`Number`,fullWidth:!1,value:t,onChange:n,setApi:r,error:o,min:1,max:65535,helperText:s,sx:{minWidth:`5.5em`}})),i&&(0,z.createElement)(C,{error:o},i===!0?`...`:(c=f)==null?(i==null?void 0:i.listening)&&E(`Correctly working on port {port}`,{port:i.port}):c))}function De({label:e,value:t,onChange:r,error:i}){let a=!t||t===`-`,o=`example.com`;return(0,z.createElement)(w,{sx:{display:`flex`}},(0,z.createElement)(b,{label:e,value:a?t:o,options:{[E`allow all`]:``,[E`forbid all`]:`-`,[E`allow some`]:o},onChange:r,error:i,sx:a?void 0:{maxWidth:`11em`}}),!a&&(0,z.createElement)(n,{label:E`Domain to allow`,value:t,placeholder:`example.com`,onChange:r,error:i,helperText:(0,z.createElement)(S)}))}function Oe({label:e,value:t,onChange:r,error:i,helperText:a,fallbackRE:o=`.*`}){let[s,c]=(0,z.useState)(``),l=typeof t==`string`;(0,z.useEffect)(()=>c(l?t:o),[t]);let u=(0,z.useId)();return(0,z.createElement)(w,{},(0,z.createElement)(w,{sx:{display:`flex`}},(0,z.createElement)(b,{label:e,value:t,onChange:r,error:i,"aria-describedby":u,options:{[E`Off`]:!1,[E`Always`]:!0,[E`RegEx`]:s},sx:l?{maxWidth:`9em`}:void 0}),l&&(0,z.createElement)(n,{label:E`User-Agent regex`,value:t,onChange:r,error:i})),(0,z.createElement)(C,{id:u},a))}export{W as default};