hfs 3.3.4 → 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 (150) hide show
  1. package/admin/assets/AccountsPage-CoiPofEv.js +4 -0
  2. package/admin/assets/ConfigFilePage-jUH2feqi.js +4 -0
  3. package/admin/assets/CustomHtmlPage-Pl_AJZrl.js +2 -0
  4. package/admin/assets/InternetPage-DIcAwhFk.js +6 -0
  5. package/admin/assets/LangPage-BwVY8QRD.js +2 -0
  6. package/admin/assets/LogsPage-CcjS12cP.js +3 -0
  7. package/admin/assets/MonitorPage-DMVFXyMe.js +2 -0
  8. package/admin/assets/OptionsPage-BB40ax9G.js +3 -0
  9. package/admin/assets/PluginsPage-CC2dINcb.js +1 -0
  10. package/admin/assets/icons-C_ux1zLA.js +1 -0
  11. package/admin/assets/index-BSSQOMG_.js +755 -0
  12. package/admin/assets/index-mI9kZlpb.css +1 -0
  13. package/admin/assets/routing-Be72yFmd.js +1 -0
  14. package/admin/index.html +2 -2
  15. package/central.json +100 -1
  16. package/frontend/assets/{dist-legacy-CnYHQXZ6.js → dist-legacy-D7Dg5w3L.js} +1 -1
  17. package/frontend/assets/index-legacy-CnazC3Dj.js +1 -0
  18. package/frontend/assets/{lib-legacy-3xYueNHt.js → lib-legacy-BxFHpdER.js} +1 -1
  19. package/frontend/assets/{sha512-legacy-Cl3f_PhN.js → sha512-legacy-C8xoQBnP.js} +1 -1
  20. package/frontend/index.html +1 -1
  21. package/npm-shrinkwrap.json +9 -9
  22. package/package.json +2 -2
  23. package/plugins/antibrute/plugin.js +62 -15
  24. package/src/acme.js +143 -50
  25. package/src/acmeConfig.js +43 -0
  26. package/src/acmeDns.js +285 -0
  27. package/src/admin-langs/embedded.js +33 -0
  28. package/src/admin-langs/hfs-admin-lang-ar.json +1149 -0
  29. package/src/admin-langs/hfs-admin-lang-bg.json +1149 -0
  30. package/src/admin-langs/hfs-admin-lang-de.json +1149 -0
  31. package/src/admin-langs/hfs-admin-lang-el.json +1149 -0
  32. package/src/admin-langs/hfs-admin-lang-en.json +1149 -0
  33. package/src/admin-langs/hfs-admin-lang-es.json +1149 -0
  34. package/src/admin-langs/hfs-admin-lang-fi.json +1149 -0
  35. package/src/admin-langs/hfs-admin-lang-fr.json +1149 -0
  36. package/src/admin-langs/hfs-admin-lang-hu.json +1149 -0
  37. package/src/admin-langs/hfs-admin-lang-it.json +1149 -0
  38. package/src/admin-langs/hfs-admin-lang-ja.json +1149 -0
  39. package/src/admin-langs/hfs-admin-lang-ko.json +1149 -0
  40. package/src/admin-langs/hfs-admin-lang-lt.json +1149 -0
  41. package/src/admin-langs/hfs-admin-lang-ms.json +1149 -0
  42. package/src/admin-langs/hfs-admin-lang-nl.json +1149 -0
  43. package/src/admin-langs/hfs-admin-lang-pl.json +1149 -0
  44. package/src/admin-langs/hfs-admin-lang-pt-br.json +1149 -0
  45. package/src/admin-langs/hfs-admin-lang-ro.json +1149 -0
  46. package/src/admin-langs/hfs-admin-lang-ru.json +1149 -0
  47. package/src/admin-langs/hfs-admin-lang-sr-latn.json +1149 -0
  48. package/src/admin-langs/hfs-admin-lang-sr.json +1149 -0
  49. package/src/admin-langs/hfs-admin-lang-th.json +1149 -0
  50. package/src/admin-langs/hfs-admin-lang-tr.json +1149 -0
  51. package/src/admin-langs/hfs-admin-lang-uk.json +1149 -0
  52. package/src/admin-langs/hfs-admin-lang-vi.json +1149 -0
  53. package/src/admin-langs/hfs-admin-lang-zh-hant.json +1149 -0
  54. package/src/admin-langs/hfs-admin-lang-zh.json +1149 -0
  55. package/src/adminApis.js +2 -2
  56. package/src/adminLang.js +51 -0
  57. package/src/api.auth.js +1 -1
  58. package/src/api.lang.js +22 -13
  59. package/src/api.log.js +5 -3
  60. package/src/api.net.js +20 -3
  61. package/src/api.plugins.js +53 -19
  62. package/src/api.vfs.js +3 -2
  63. package/src/argv.js +4 -0
  64. package/src/auth.js +13 -9
  65. package/src/basicWeb.js +6 -2
  66. package/src/block.js +1 -1
  67. package/src/config.js +1 -0
  68. package/src/connections.js +4 -7
  69. package/src/consoleLog.js +4 -0
  70. package/src/const.js +1 -1
  71. package/src/cross-const.js +8 -6
  72. package/src/cross.js +16 -2
  73. package/src/customHtml.js +3 -2
  74. package/src/frontEndApis.js +44 -7
  75. package/src/github.js +6 -37
  76. package/src/i18n.js +16 -4
  77. package/src/index.js +4 -3
  78. package/src/lang.js +21 -9
  79. package/src/langs/embedded.js +3 -2
  80. package/src/langs/hfs-lang-ar.json +7 -2
  81. package/src/langs/hfs-lang-bg.json +7 -2
  82. package/src/langs/hfs-lang-de.json +7 -2
  83. package/src/langs/hfs-lang-el.json +7 -2
  84. package/src/langs/hfs-lang-en.json +189 -0
  85. package/src/langs/hfs-lang-es.json +9 -4
  86. package/src/langs/hfs-lang-fi.json +8 -3
  87. package/src/langs/hfs-lang-fr.json +12 -7
  88. package/src/langs/hfs-lang-hu.json +7 -2
  89. package/src/langs/hfs-lang-it.json +11 -5
  90. package/src/langs/hfs-lang-ja.json +7 -2
  91. package/src/langs/hfs-lang-ko.json +7 -2
  92. package/src/langs/hfs-lang-lt.json +7 -2
  93. package/src/langs/hfs-lang-ms.json +9 -2
  94. package/src/langs/hfs-lang-nl.json +7 -2
  95. package/src/langs/hfs-lang-pl.json +7 -2
  96. package/src/langs/hfs-lang-pt-br.json +9 -4
  97. package/src/langs/hfs-lang-ro.json +7 -2
  98. package/src/langs/hfs-lang-ru.json +9 -4
  99. package/src/langs/hfs-lang-sr-latn.json +7 -2
  100. package/src/langs/hfs-lang-sr.json +7 -2
  101. package/src/langs/hfs-lang-th.json +8 -3
  102. package/src/langs/hfs-lang-tr.json +8 -3
  103. package/src/langs/hfs-lang-uk.json +16 -11
  104. package/src/langs/hfs-lang-vi.json +7 -2
  105. package/src/langs/hfs-lang-zh-hant.json +189 -0
  106. package/src/langs/hfs-lang-zh.json +97 -95
  107. package/src/listen.js +6 -3
  108. package/src/log.js +7 -1
  109. package/src/middlewares.js +87 -31
  110. package/src/multipartUpload.js +21 -0
  111. package/src/perm.js +38 -20
  112. package/src/pluginCatalog.js +66 -0
  113. package/src/pluginUsage.js +64 -0
  114. package/src/plugins.js +14 -2
  115. package/src/roots.js +22 -13
  116. package/src/serveFile.js +28 -11
  117. package/src/serveGuiAndSharedFiles.js +6 -6
  118. package/src/serveGuiFiles.js +7 -4
  119. package/src/update.js +51 -18
  120. package/src/upload.js +49 -8
  121. package/src/util-http.js +31 -5
  122. package/src/vfs.js +93 -4
  123. package/src/webdav.js +14 -8
  124. package/admin/assets/AccountsPage-CnZGcUox.js +0 -6
  125. package/admin/assets/ArrayField-F7fpCJIv.js +0 -2
  126. package/admin/assets/ConfigFilePage-CEgZ8Wq4.js +0 -4
  127. package/admin/assets/CustomHtmlPage-DDUNxF1H.js +0 -2
  128. package/admin/assets/DataTable-D_TeYxvk.js +0 -119
  129. package/admin/assets/DateTimeField-CsIdkTqz.js +0 -1
  130. package/admin/assets/EventManager--HiXIAY0.js +0 -1
  131. package/admin/assets/FileField-HsDCdr5S.js +0 -117
  132. package/admin/assets/InternetPage-DDwKrXJH.js +0 -8
  133. package/admin/assets/LangPage-Dd8hi_9K.js +0 -2
  134. package/admin/assets/LogsPage-BcPfugNY.js +0 -4
  135. package/admin/assets/MenuButton-cuehhVig.js +0 -1
  136. package/admin/assets/MonitorPage-Bre9d6MA.js +0 -2
  137. package/admin/assets/OptionsPage-Dd_cRDf4.js +0 -3
  138. package/admin/assets/PluginsPage-DJiseD4G.js +0 -1
  139. package/admin/assets/Tabs-C87E85Fh.js +0 -1
  140. package/admin/assets/TextEditor-BeS-mR2I.css +0 -1
  141. package/admin/assets/TextEditor-Cr0VI9Xi.js +0 -34
  142. package/admin/assets/VfsPathField-D1BkfhAM.js +0 -1
  143. package/admin/assets/agentIcons-CDoUTPUC.js +0 -1
  144. package/admin/assets/baseUrl-CnrV7Huu.js +0 -2
  145. package/admin/assets/icons-DJJYoK9E.js +0 -1
  146. package/admin/assets/index-11n5z0Ez.css +0 -1
  147. package/admin/assets/index-DwXlUHAs.js +0 -477
  148. package/admin/assets/routing-BQpoUdjq.js +0 -1
  149. package/frontend/assets/index-legacy-JYULclbY.js +0 -1
  150. package/src/langs/hfs-lang-zh-tw.json +0 -183
package/src/upload.js CHANGED
@@ -5,7 +5,11 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.dontOverwriteUploading = exports.minAvailableMb = exports.deleteUnfinishedUploadsAfter = void 0;
7
7
  exports.getUploadMeta = getUploadMeta;
8
+ exports.saveUploadMeta = saveUploadMeta;
9
+ exports.setUploadApproved = setUploadApproved;
8
10
  exports.getUploadTempFor = getUploadTempFor;
11
+ exports.isUploading = isUploading;
12
+ exports.whileUploadMetaPending = whileUploadMetaPending;
9
13
  exports.uploadWriter = uploadWriter;
10
14
  const vfs_1 = require("./vfs");
11
15
  const const_1 = require("./const");
@@ -25,6 +29,7 @@ const expiringCache_1 = require("./expiringCache");
25
29
  const first_1 = require("./first");
26
30
  const uploadOwners_1 = require("./uploadOwners");
27
31
  const webdav_1 = require("./webdav");
32
+ const adminApis_1 = require("./adminApis");
28
33
  exports.deleteUnfinishedUploadsAfter = (0, config_1.defineConfig)(misc_1.CFG.delete_unfinished_uploads_after, 86_400);
29
34
  exports.minAvailableMb = (0, config_1.defineConfig)(misc_1.CFG.min_available_mb, 100);
30
35
  exports.dontOverwriteUploading = (0, config_1.defineConfig)(misc_1.CFG.dont_overwrite_uploading, true);
@@ -56,10 +61,18 @@ const ATTR_UPLOADER = 'uploader';
56
61
  function getUploadMeta(path) {
57
62
  return (0, misc_1.loadFileAttr)(path, ATTR_UPLOADER);
58
63
  }
59
- function setUploadMeta(path, ctx) {
60
- return (0, misc_1.storeFileAttr)(path, ATTR_UPLOADER, {
64
+ function saveUploadMeta(path, meta) {
65
+ return (0, misc_1.storeFileAttr)(path, ATTR_UPLOADER, meta);
66
+ }
67
+ async function setUploadApproved(path, approved) {
68
+ const meta = await getUploadMeta(path);
69
+ return Boolean(meta && await saveUploadMeta(path, { ...meta, approved }));
70
+ }
71
+ function fillUploadMeta(path, ctx) {
72
+ return saveUploadMeta(path, {
61
73
  username: (0, auth_1.getCurrentUsername)(ctx) || undefined,
62
74
  ip: ctx.ip,
75
+ approved: Boolean((0, adminApis_1.ctxAdminAccess)(ctx)),
63
76
  });
64
77
  }
65
78
  function getUploadTempFor(fullPath) {
@@ -67,6 +80,24 @@ function getUploadTempFor(fullPath) {
67
80
  }
68
81
  const diskSpaceCache = (0, expiringCache_1.expiringCache)(3_000); // invalidate shortly
69
82
  const uploadingFiles = new Map();
83
+ const uploadMetaPending = new Set();
84
+ function isUploading(path) {
85
+ const key = (0, vfs_1.normalizeFilename)(path);
86
+ return (0, path_1.basename)(path).startsWith(const_1.UPLOAD_TEMP_PREFIX)
87
+ || uploadingFiles.has(key)
88
+ || uploadingFiles.has((0, vfs_1.normalizeFilename)(getUploadTempFor(path))) // active uploads are keyed by their temporary file
89
+ || uploadMetaPending.has(key);
90
+ }
91
+ async function whileUploadMetaPending(path, cb) {
92
+ const key = (0, vfs_1.normalizeFilename)(path);
93
+ uploadMetaPending.add(key);
94
+ try {
95
+ return await cb();
96
+ }
97
+ finally {
98
+ uploadMetaPending.delete(key);
99
+ }
100
+ }
70
101
  // initially sync for formidable; still sync to avoid async races and PUT piping gaps
71
102
  function uploadWriter(base, baseUri, filename, ctx) {
72
103
  if (!filename || !(0, misc_1.isValidFileName)(filename) || !filename)
@@ -123,7 +154,7 @@ function uploadWriter(base, baseUri, filename, ctx) {
123
154
  const createdFolders = [];
124
155
  const firstCreated = !dir.endsWith(':\\') && fs_1.default.mkdirSync(dir, { recursive: true });
125
156
  if (firstCreated) {
126
- setUploadMeta(dir, ctx);
157
+ fillUploadMeta(dir, ctx);
127
158
  // recursive mkdir returns the first created directory; existing ancestors must not receive ownership
128
159
  const firstCreatedPath = (0, path_1.toNamespacedPath)((0, path_1.resolve)(firstCreated)); // Node 24 mkdir can return a namespace-prefixed Windows path
129
160
  let source = (0, path_1.resolve)(dir);
@@ -219,12 +250,16 @@ function uploadWriter(base, baseUri, filename, ctx) {
219
250
  await new Promise(res => fileStream.close(res)); // this only seems necessary on Windows
220
251
  for (const { uri, source } of createdFolders)
221
252
  await (0, uploadOwners_1.setUploadOwner)(uri, ctx, source);
222
- if (errored)
223
- return;
224
253
  if (simulate)
225
254
  return (0, promises_1.rm)(tempName).catch(() => { });
255
+ if (!await fillUploadMeta(tempName, ctx)) {
256
+ await (0, promises_1.rm)(tempName).catch(() => { });
257
+ return fail(const_1.HTTP_SERVER_ERROR);
258
+ }
259
+ if (errored)
260
+ return;
226
261
  if (ctx.isAborted()) { // in the very unlikely case the connection is interrupted between last-byte and here, we still consider it unfinished, as the client had no way to know, and will resume, but it would get an error if we finish the process
227
- const sec = exports.deleteUnfinishedUploadsAfter.get();
262
+ const sec = first_1.quitting ? undefined : exports.deleteUnfinishedUploadsAfter.get(); // preserve uploads interrupted by shutdown for resuming
228
263
  await (0, uploadOwners_1.setUploadOwner)(tempOwnerUri, ctx, tempName, lodash_1.default.isNumber(sec) ? Date.now() + sec * 1000 : null);
229
264
  return lodash_1.default.isNumber(sec) && delayedDelete(tempName, sec, tempOwnerUri);
230
265
  }
@@ -254,11 +289,16 @@ function uploadWriter(base, baseUri, filename, ctx) {
254
289
  () => (0, promises_1.rename)(tempName, dest).then(() => true, e => e?.code !== 'EBUSY' && Promise.reject(e)), { timeout: 10_000 });
255
290
  if (!done)
256
291
  throw 'EBUSY';
292
+ await (0, misc_1.moveStoredFileAttrs)(tempName, dest);
257
293
  (0, uploadOwners_1.deleteUploadOwner)(tempOwnerUri); // the temp URI no longer exists after rename; final ownership is recorded below
258
294
  if (mtime) // so we use it to touch the file
259
295
  await (0, promises_1.utimes)(dest, Date.now() / 1000, mtime / 1000);
260
296
  obj.fullPath = ctx.state.uploadDestinationPath = dest;
261
- void setUploadMeta(dest, ctx);
297
+ if (!await fillUploadMeta(dest, ctx)) {
298
+ await (0, promises_1.rename)(dest, tempName);
299
+ await (0, misc_1.moveStoredFileAttrs)(dest, tempName);
300
+ throw Error("couldn't store upload metadata");
301
+ }
262
302
  if (ctx.query.comment)
263
303
  void (0, comments_1.setCommentFor)(dest, String(ctx.query.comment));
264
304
  obj.uri = (0, misc_1.enforceFinal)('/', baseUri) + (0, misc_1.pathEncode)((0, path_1.basename)(dest));
@@ -272,8 +312,9 @@ function uploadWriter(base, baseUri, filename, ctx) {
272
312
  cb(obj);
273
313
  }
274
314
  catch (err) {
275
- void setUploadMeta(tempName, ctx);
315
+ void fillUploadMeta(tempName, ctx);
276
316
  console.error("Couldn't rename temp to", dest, String(err));
317
+ fail(const_1.HTTP_SERVER_ERROR);
277
318
  }
278
319
  }
279
320
  finally {
package/src/util-http.js CHANGED
@@ -38,6 +38,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
38
38
  };
39
39
  Object.defineProperty(exports, "__esModule", { value: true });
40
40
  exports.stream2string = void 0;
41
+ exports.readTextLimited = readTextLimited;
42
+ exports.readRequestBodyLimited = readRequestBodyLimited;
41
43
  exports.httpString = httpString;
42
44
  exports.httpWithBody = httpWithBody;
43
45
  exports.httpStream = httpStream;
@@ -51,6 +53,29 @@ const consumers_1 = require("node:stream/consumers");
51
53
  Object.defineProperty(exports, "stream2string", { enumerable: true, get: function () { return consumers_1.text; } });
52
54
  const tls = __importStar(require("node:tls"));
53
55
  const cross_1 = require("./cross");
56
+ const cross_const_1 = require("./cross-const");
57
+ async function readTextLimited(stream, maxBytes) {
58
+ if (Number(stream.headers['content-length']) > maxBytes)
59
+ return { error: 'too_large' };
60
+ const chunks = [];
61
+ let size = 0;
62
+ // keep the socket alive so callers can send the rejection before closing an undrained request
63
+ for await (const chunk of stream.iterator({ destroyOnReturn: false })) {
64
+ const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
65
+ size += buffer.length;
66
+ if (size > maxBytes)
67
+ return { error: 'too_large' };
68
+ chunks.push(buffer);
69
+ }
70
+ return { text: new TextDecoder().decode(Buffer.concat(chunks, size)) };
71
+ }
72
+ async function readRequestBodyLimited(ctx, maxBytes) {
73
+ const body = await readTextLimited(ctx.req, maxBytes);
74
+ if (!('error' in body))
75
+ return body;
76
+ ctx.set('Connection', 'close'); // an unread rejected body makes this connection unsafe to reuse
77
+ ctx.status = cross_const_1.HTTP_PAYLOAD_TOO_LARGE;
78
+ }
54
79
  async function httpString(url, { maxBytes = 10 * cross_1.MB, ...options } = {}) {
55
80
  const stream = await httpStream(url, options);
56
81
  if (maxBytes === Infinity)
@@ -85,12 +110,13 @@ function httpStream(url, { body, proxy, jar, noRedirect, httpThrow = true, ...op
85
110
  options.headers['user-agent'] = httpStream.defaultUA;
86
111
  if (body) {
87
112
  options.method ||= 'POST';
88
- if (lodash_1.default.isPlainObject(body)) {
89
- options.headers['content-type'] ??= 'application/json';
90
- body = JSON.stringify(body);
91
- }
92
- if (!(body instanceof node_stream_1.Readable))
113
+ if (!(body instanceof node_stream_1.Readable)) {
114
+ if (typeof body === 'object' && !(body instanceof Buffer)) {
115
+ options.headers['content-type'] ??= 'application/json';
116
+ body = JSON.stringify(body);
117
+ }
93
118
  options.headers['content-length'] ??= Buffer.byteLength(body);
119
+ }
94
120
  }
95
121
  const { auth, ...parsed } = parseHttpUrl(url);
96
122
  const hostJar = jar && (jar[parsed.hostname || ''] ||= {});
package/src/vfs.js CHANGED
@@ -43,6 +43,7 @@ const walkDir_1 = require("./walkDir");
43
43
  const node_stream_1 = require("node:stream");
44
44
  const adminApis_1 = require("./adminApis");
45
45
  const showHiddenFiles = (0, config_1.defineConfig)(misc_1.CFG.show_hidden_files, false);
46
+ const sourceNames = new Map();
46
47
  function setVfsPath(node, vfsPath, parent) {
47
48
  return (0, misc_1.setHidden)(node, {
48
49
  vfsPath: (0, misc_1.enforceFinal)('/', parent?.vfsPath) + (0, misc_1.pathEncode)(vfsPath)
@@ -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;
@@ -190,6 +197,30 @@ async function getNodeByName(name, parent, assumeMissingToBeFolder = false) {
190
197
  // does the tree node have a child that goes by this name, otherwise attempt disk
191
198
  let virtualName = name;
192
199
  let child = parent.children?.find(isSameFilenameAs(name));
200
+ const sameName = isSameFilenameAs(name);
201
+ const sourceName = Object.entries(parent.rename || {}).find(([, to]) => sameName(to))?.[0] || name;
202
+ if (!child) {
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;
207
+ if (!child && const_1.IS_WINDOWS && parent.source) {
208
+ const aliases = await windowsPathNames((0, path_1.join)(parent.source, sourceName), parent.source);
209
+ if (!aliases)
210
+ return;
211
+ child = findSourceChild(aliases);
212
+ if (!child) {
213
+ const normalized = new Set(aliases.map(normalizeFilename));
214
+ const candidates = parent.children?.filter(candidate => candidate.source
215
+ && normalized.has(normalizeFilename((0, path_1.basename)(candidate.source)))) || [];
216
+ if (candidates.length > 1)
217
+ return; // an unverified physical alias must identify one VFS child
218
+ const [candidate] = candidates;
219
+ if (candidate?.source && await isSameFilePath((0, path_1.dirname)(candidate.source), parent.source))
220
+ child = candidate;
221
+ }
222
+ }
223
+ }
193
224
  if (child) { // found as vfs node
194
225
  virtualName = getNodeName(child);
195
226
  await setIsFolder(child); // in case it's pointing to a folder that didn't exist at loading time
@@ -203,7 +234,6 @@ async function getNodeByName(name, parent, assumeMissingToBeFolder = false) {
203
234
  const ret = {};
204
235
  let onDisk = name;
205
236
  if (parent.rename) { // reverse the mapping
206
- const sameName = isSameFilenameAs(name);
207
237
  const entries = Object.entries(parent.rename);
208
238
  // search display names first: with { A: B, C: A }, A must resolve to C rather than be hidden
209
239
  const asDisplayName = entries.find(([, to]) => sameName(to));
@@ -212,12 +242,17 @@ async function getNodeByName(name, parent, assumeMissingToBeFolder = false) {
212
242
  virtualName = asDisplayName[1];
213
243
  }
214
244
  else {
215
- const asPhysicalName = entries.find(([from]) => sameName(from));
245
+ if (!await canonicalizeWindowsAlias())
246
+ return;
247
+ const sameSourceName = isSameFilenameAs(onDisk);
248
+ const asPhysicalName = entries.find(([from]) => sameSourceName(from));
216
249
  if (asPhysicalName)
217
250
  return; // a VFS rename replaces the original public name
218
251
  }
219
252
  ret.rename = renameUnderPath(parent.rename, onDisk);
220
253
  }
254
+ else if (!await canonicalizeWindowsAlias())
255
+ return;
221
256
  if (!(0, misc_1.isValidFileName)(onDisk))
222
257
  return;
223
258
  ret.source = (0, path_1.join)(parent.source, onDisk);
@@ -228,6 +263,22 @@ async function getNodeByName(name, parent, assumeMissingToBeFolder = false) {
228
263
  if (assumeMissingToBeFolder)
229
264
  ret.isFolder ??= true;
230
265
  return ret;
266
+ async function canonicalizeWindowsAlias() {
267
+ if (!const_1.IS_WINDOWS || !name.includes('~'))
268
+ return true;
269
+ // Windows exposes 8.3 aliases that must keep the long name's VFS identity
270
+ const longPath = await convertWindowsPath((0, path_1.join)(parent.source, name), true);
271
+ if (!longPath)
272
+ return true; // new names containing '~' are still valid
273
+ if (!await isSameFilePath((0, path_1.dirname)(longPath), parent.source))
274
+ return false;
275
+ onDisk = virtualName = (0, path_1.basename)(longPath);
276
+ return true;
277
+ }
278
+ }
279
+ function findSourceChild(names = [sourceName]) {
280
+ const normalized = new Set(names.map(normalizeFilename));
281
+ return parent.children?.find(x => getSourceNames(x, parent).some(source => normalized.has(normalizeFilename(source))));
231
282
  }
232
283
  }
233
284
  function isCommentsFileName(name) {
@@ -267,8 +318,42 @@ async function reviewVfs() {
267
318
  usedNames.add(normalizeFilename(getNodeName(child)));
268
319
  }
269
320
  await Promise.allSettled(node.children.map(recur));
321
+ await Promise.allSettled(node.children.map(child => setSourceNames(child, node)));
270
322
  })(exports.vfs.compiled());
271
323
  }
324
+ async function setSourceNames(node, parent) {
325
+ if (!node.source || !parent.source)
326
+ return;
327
+ const sameParent = normalizeFilename((0, path_1.dirname)((0, path_1.resolve)(node.source))) === normalizeFilename((0, path_1.resolve)(parent.source));
328
+ if (!sameParent && (!const_1.IS_WINDOWS || !await isSameFilePath((0, path_1.dirname)(node.source), parent.source)))
329
+ return;
330
+ const names = [(0, path_1.basename)(node.source)];
331
+ const key = sourceNamesKey(node, parent);
332
+ if (const_1.IS_WINDOWS) {
333
+ const converted = await Promise.all([convertWindowsPath(node.source), convertWindowsPath(node.source, true)]);
334
+ names.push(...converted.map(x => x && (0, path_1.basename)(x)).filter(Boolean));
335
+ }
336
+ sourceNames.set(key, lodash_1.default.uniq(names));
337
+ }
338
+ function convertWindowsPath(path, isLong = false) {
339
+ return new Promise(resolvePromise => {
340
+ if (!fswin_1.default.convertPath(path, resolvePromise, isLong))
341
+ resolvePromise(undefined);
342
+ });
343
+ }
344
+ async function windowsPathNames(path, parent) {
345
+ const converted = await Promise.all([convertWindowsPath(path), convertWindowsPath(path, true)]);
346
+ const existing = converted.find(Boolean);
347
+ if (existing && !await isSameFilePath((0, path_1.dirname)(existing), parent))
348
+ return;
349
+ return lodash_1.default.uniq([(0, path_1.basename)(path), ...converted.map(x => x && (0, path_1.basename)(x)).filter(Boolean)]);
350
+ }
351
+ function getSourceNames(node, parent) {
352
+ return node.source && parent.source ? sourceNames.get(sourceNamesKey(node, parent)) || [] : [];
353
+ }
354
+ function sourceNamesKey(node, parent) {
355
+ return normalizeFilename((0, path_1.resolve)(parent.source)) + '\0' + normalizeFilename((0, path_1.resolve)(node.source));
356
+ }
272
357
  exports.saveVfs = (0, misc_1.debounceAsync)(async () => {
273
358
  await reviewVfs(); // refresh runtime-derived folder flags before saving mutated VFS state
274
359
  exports.vfs.set(exports.vfs.compiled()); // sync the mutable runtime tree back to config state before persisting it
@@ -391,13 +476,16 @@ async function* walkNode(parent, { ctx, depth = Infinity, prefixPath = '', requi
391
476
  started = true;
392
477
  const { source } = parent;
393
478
  const taken = new Set();
479
+ const takenSources = new Set();
394
480
  const visitLater = [];
395
481
  const childrenWorking = parent.children?.length && Promise.all(parent.children.map(async (child) => {
396
482
  if (ctx?.isAborted())
397
483
  return;
398
484
  const nodeName = getNodeName(child);
399
485
  const name = prefixPath + nodeName;
400
- taken?.add(normalizeFilename(name));
486
+ taken.add(normalizeFilename(name));
487
+ for (const sourceName of getSourceNames(child, parent))
488
+ takenSources.add(normalizeFilename(prefixPath + sourceName));
401
489
  const item = setVfsPath({ ...child, original: child, name, parent }, name, parent);
402
490
  if (await cantSee(item, parent, nodeName))
403
491
  return;
@@ -444,7 +532,8 @@ async function* walkNode(parent, { ctx, depth = Infinity, prefixPath = '', requi
444
532
  ? evaluatedParent.virtualPath + '/' + virtualName
445
533
  : virtualName;
446
534
  const name = prefixPath + virtualPath;
447
- if (taken.has(normalizeFilename(name))) // taken by vfs node above
535
+ if (taken.has(normalizeFilename(name))
536
+ || takenSources.has(normalizeFilename(prefixPath + path))) // taken by vfs node above
448
537
  return false; // false just in case it's a folder
449
538
  const item = setVfsPath({
450
539
  name: virtualName,
package/src/webdav.js CHANGED
@@ -6,7 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.webdav = void 0;
7
7
  exports.releaseWebdavLock = releaseWebdavLock;
8
8
  exports.isWebdavLocked = isWebdavLocked;
9
- const consumers_1 = require("node:stream/consumers");
9
+ const util_http_1 = require("./util-http");
10
10
  const vfs_1 = require("./vfs");
11
11
  const cross_1 = require("./cross");
12
12
  const stream_1 = require("stream");
@@ -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();
@@ -315,11 +315,14 @@ const webdav = async (ctx, next) => {
315
315
  setWebdavHeaders(true);
316
316
  return;
317
317
  }
318
- const body = ctx.length || ctx.get('content-length') || ctx.get('transfer-encoding') ? await (0, consumers_1.text)(ctx.req) : '';
318
+ const body = await (0, util_http_1.readRequestBodyLimited)(ctx, 1024 * 1024);
319
+ if (!body)
320
+ return;
321
+ const bodyText = body.text;
319
322
  const token = getProvidedLockToken();
320
323
  let seconds = Number(ctx.get('timeout').split(',').find(x => /^Second-\d+$/i.test(x.trim()))?.trim().split('-', 2)[1]);
321
324
  seconds = lodash_1.default.clamp(seconds || LOCK_DEFAULT_SECONDS, 1, LOCK_MAX_SECONDS);
322
- if (!body) {
325
+ if (!bodyText) {
323
326
  // Finder and similar clients refresh an existing lock by sending LOCK without a body
324
327
  if (!token)
325
328
  return ctx.status = cross_1.HTTP_BAD_REQUEST;
@@ -338,7 +341,7 @@ const webdav = async (ctx, next) => {
338
341
  ctx.body = renderLockResponse(lock.token, lock.seconds);
339
342
  return;
340
343
  }
341
- const lockinfo = (0, cross_1.try_)(() => xmlParser.parse(body).lockinfo);
344
+ const lockinfo = (0, cross_1.try_)(() => xmlParser.parse(bodyText).lockinfo);
342
345
  const scope = lodash_1.default.keys(lockinfo?.lockscope)[0];
343
346
  const type = lodash_1.default.keys(lockinfo?.locktype)[0];
344
347
  if (!scope || !type)
@@ -416,8 +419,11 @@ const webdav = async (ctx, next) => {
416
419
  setWebdavHeaders(true);
417
420
  return;
418
421
  }
419
- const body = ctx.length || ctx.get('content-length') || ctx.get('transfer-encoding') ? await (0, consumers_1.text)(ctx.req) : '';
420
- const props = (0, cross_1.try_)(() => parseProppatchProps(body)) || [];
422
+ const body = await (0, util_http_1.readRequestBodyLimited)(ctx, 1024 * 1024);
423
+ if (!body)
424
+ return;
425
+ const bodyText = body.text;
426
+ const props = (0, cross_1.try_)(() => parseProppatchProps(bodyText)) || [];
421
427
  if (!props.length)
422
428
  return ctx.status = cross_1.HTTP_BAD_REQUEST;
423
429
  const statuses = [];
@@ -1,6 +0,0 @@
1
- import{i as e}from"./rolldown-runtime-CacB07dV.js";import{t}from"./DateTimeField-CsIdkTqz.js";import{n,r,t as i}from"./MenuButton-cuehhVig.js";import{n as a,t as o}from"./VfsPathField-D1BkfhAM.js";import{Bn as s,C as c,Ct as l,Da as u,E as d,Ea as f,Et as p,F as m,Ft as h,H as g,Ir as _,Lt as v,M as y,Ma as b,N as x,O as S,Oa as C,Pa as w,R as T,S as ee,St as E,T as D,Ta as O,U as k,Ut as A,W as j,Wr as M,Yt as N,_ as te,_i as P,_r as ne,ai as F,en as I,fr as re,ft as L,gt as ie,ht as ae,it as R,j as z,jr as oe,ka as se,lr as B,lt as ce,mt as V,ni as le,nr as ue,q as de,ri as fe,st as pe,tr as me,tt as H,vi as U,w as he,y as W,zt as ge}from"./index-DwXlUHAs.js";import{$ as _e,J as ve,Q as ye,X as be,Y as xe,Z as Se,et as G,it as Ce,nt as we,rt as K,tt as Te}from"./icons-DJJYoK9E.js";var q=e(w());u();var Ee=[`password`,`password2`,`adminActualAccess`,`hasPassword`,`invalidated`,`canLogin`,`members`];function De({account:e,done:n,groups:r,addToBar:i,reload:a}){var u,m;let{username:h}=R(),[_,v]=(0,q.useState)(e),[x,S]=(0,q.useState)([]),C=I();(0,q.useEffect)(()=>{var t;v(e),S(r.filter(t=>t!==e.username)),C||(t=k.current)==null||(t=t.querySelector(`input`))==null||t.focus()},[JSON.stringify(e)]);let w=!e.username,{isGroup:T}=_,k=(0,q.useRef)(),{members:A}=e,j=(u=e.plugin)==null?void 0:u.auth;return(0,q.createElement)(ce,{key:e.username,formRef:k,values:_,set(e,t){v(n=>f(f({},n),{},{[t]:e}))},barSx:f({gap:2,width:`100%`},de()),stickyBar:!0,addToBar:[!w&&(0,q.createElement)(c,f(f({icon:K,title:`Delete`,confirm:`Delete ${e.username}?`},h===e.username&&{disabled:!0,title:`Cannot delete current account`}),{},{onClick:()=>B(`del_account`,{username:e.username}).then(a)})),(0,q.createElement)(c,{icon:xe,title:`Invalidate past sessions${s(`
2
- (already invalidated sessions before `,E(e.invalidated||0),`)`)}`,confirm:`Invalidate all sessions up to now for "${e.username}"?`,doneMessage:!0,onClick:()=>B(`invalidate_sessions`,{username:e.username}).then(a)}),...me(i)],fields:[{k:`username`,label:T?`Group name`:void 0,autoComplete:`off`,required:!0,md:T&&!j?12:4,getError:t=>t!==e.username&&B(`get_account`,{username:t}).then(t=>(t==null?void 0:t.username)===e.username?`usernames are case-insensitive`:`already used`,()=>!1)},j&&{k:``,md:8,comp:(0,q.createElement)(U,{severity:`info`},` Authentication handled by a plugin`)},!T&&!j&&{k:`password`,xs:6,md:4,type:`password`,autoComplete:`new-password`,required:w,label:w?`Password`:`Change password`},!T&&!j&&{k:`password2`,xs:6,md:4,type:`password`,autoComplete:`new-password`,label:`Repeat password`,getError:(e,{values:t})=>(e||``)!==(t.password||``)&&`Enter same password`},{k:`disabled`,comp:L,fromField:e=>!e,toField:e=>!e,label:`Enabled`,xs:12,sm:6,lg:4,helperText:_.disabled||_.canLogin!==!1?`Login is prevented if account is disabled, or all its groups are disabled`:(0,q.createElement)(F,{sx:{color:`warning.main`},component:`span`},new Date(e.expire)<new Date?`Login is prevented because account is expired`:`Login is prevented because all of its groups are disabled`)},{k:`ignore_limits`,comp:L,xs:12,sm:6,lg:4,helperText:_.ignore_limits?`Speed limits don't apply to this account`:`Speed limits apply to this account`},f({k:`admin`,comp:L,fromField:e=>e||null,label:`Admin-panel access`,xs:12,sm:6,lg:4,helperText:`To access THIS interface you are using right now`},!e.admin&&e.adminActualAccess&&{value:!0,disabled:!0,helperText:`This permission is inherited. To disable it, act on the groups.`}),!T&&{k:`require_password_change`,comp:L,xs:12,sm:6,lg:4,helperText:`At next login, but can be dismissed`},{k:`disable_password_change`,label:`Password change`,comp:ie,xs:12,sm:6,lg:4,defaultValue:null,options:{[`Default (${_.canChangePassword?`Allowed`:`Disabled`})`]:null,Allowed:!1,Disabled:!0}},A?T&&!A.length?(0,q.createElement)(F,{},`No members`):A.length>0&&(0,q.createElement)(ee,{gap:0,flexWrap:`wrap`},`${A.length} members: `,N(`,\xA0`,(m=e.members)==null?void 0:m.map(e=>(0,q.createElement)(r.includes(e)?`i`:`span`,{},e))),(0,q.createElement)(W,{icon:K,confirm:`Delete ${e.members.length} accounts?`,onClick:()=>B(`del_account`,{username:e.members}).then(a),sx:{verticalAlign:`text-top`}})):null,T&&(0,q.createElement)(U,{severity:`info`},`To add users to this group, select the user and then click "Inherit"`),{k:`belongs`,comp:ae,label:`Inherit from groups`,options:x,sm:6,lg:4,helperText:`Specify groups to inherit permissions from`+(T?`. A group can inherit from another group`:``)+(x.length?``:`. Now disabled because there are no groups to select, create one first.`)},{k:`allow_net`,comp:d,label:`Allowed network address`,sm:6,lg:4,placeholder:`any address`},!T&&{k:`auto_login_net`,comp:d,label:`Auto-login by IP address`,sm:6,lg:4,placeholder:`none`,helperText:(0,q.createElement)(D,{onClick:()=>g(`IP addresses and localhost work without additional configuration.
3
- If you access HFS using a hostname, configure it under Internet → Address, as the Main address or in Domain roots. Include the port if it appears in the address.
4
- This prevents a malicious website from using your browser's authorized IP address to log in through DNS rebinding. Only configure domains you trust; avoid overly broad Domain roots wildcards.
5
- For other hostnames, automatic login is skipped, but login with credentials remains available.`,{title:`Auto-login by IP address`})},`May require additional configuration`)},{k:`redirect`,comp:o,placeholder:`no`,sm:6,lg:4,helperText:`If you want this account to be redirected to a specific folder/address (or even file) at login time`},{k:`expire`,label:`Expiration`,sm:6,lg:4,comp:t,toField:e=>e&&new Date(e),helperText:`When expired, login won't be allowed`},f(f({k:`days_to_live`,sm:6,lg:4,comp:V,step:`any`,min:1/1e3},_.expire&&{xs:12,disabled:!0,sx:{opacity:.2}}),{},{helperText:`Used to set expiration on first login`}),{k:`notes`,multiline:!0,sm:6,lg:4}],onError:g,save:f(f({},y(l(_,e))),{},{onClick(){return b(function*(){var t;let{password:r=``,password2:i,adminActualAccess:a,hasPassword:o,invalidated:s,canLogin:c,members:l}=_,u=O(_,Ee),d=((t=k.current)==null?void 0:t.querySelector(`button.saveBtn`))||void 0;if(w){let e=yield B(`add_account`,u);if(r)try{yield p(_.username,r)}catch(e){throw B(`del_account`,{username:_.username}),e}n(e==null?void 0:e.username,d);return}let m=yield B(`set_account`,{username:e.username,changes:u});r&&(yield p(_.username,r),v(e=>f(f({},e),{},{password:``,password2:``}))),e.username===h&&(H.username=m.username),n(m==null?void 0:m.username,d)})()}})})}var J=e(re());se(),u();var Y={fieldSeparator:`,`,lineSeparator:`
6
- `,quote:`"`},X={field:0,fieldOffset:0,line:0,lineOffset:0,quoted:!1,appendCell:!1,appendField:!1,appendRow:!1},Z=class extends Error{constructor(e,t){super(`Invalid CSV at ${e}:${t}`)}},Oe=class{constructor(e=Y){C(this,`_rows`,[]),C(this,`_row`,[]),C(this,`_cell`,``),C(this,`_options`,Y),C(this,`_state`,f({},X)),C(this,`_index`,0),C(this,`_current`,``),C(this,`_previous`,``),C(this,`_quoteState`,f({},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 Z(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=f({},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 Z(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=f({},X),this._index=0,this._current=``,this._previous=``,this._quoteState=f({},X)}};u();function ke(e){return Q.apply(this,arguments)}function Q(){return Q=b(function*(e){v(function(){var t=b(function*(t){let n=t==null?void 0:t[0];if(!n)return;let r=yield h(n);if(!r)return;let i=new Oe().parse(r.trim()),a={comp:V,min:1,max:9,xs:6,typing:!0},o={skipFirstLines:0,usernameColumn:1,passwordColumn:2,groupColumn:3,redirectColumn:4,overwriteExistingAccounts:!1},c=yield j({title:`Import accounts from CSV`,dialogProps:{maxWidth:`sm`},values:o,form:e=>{let t=i[e.skipFirstLines||0]||[],n=u(t,f(f({},o),e));return{save:{startIcon:(0,q.createElement)(ve),children:`Go`},fields:[(0,q.createElement)(F,{sx:{p:1}},`Total lines:`,i.length),{k:`skipFirstLines`,comp:V,max:i.length-1,typing:!0,md:6,getError:e=>e!=null&&!Number.isInteger(e)&&`Enter an integer`,helperText:(0,q.createElement)(q.Fragment,{},`First line: `,(0,q.createElement)(`code`,{},t.join(`, `)))},{k:`overwriteExistingAccounts`,comp:L,md:6},f(f({k:`usernameColumn`},a),{},{helperText:(0,q.createElement)(q.Fragment,{},`First username: `,n.u)}),f(f({k:`passwordColumn`},a),{},{helperText:(0,q.createElement)(q.Fragment,{},`First password: `,n.p)}),f(f({k:`groupColumn`},a),{},{helperText:(0,q.createElement)(q.Fragment,{},`First group: `,n.g)}),f(f({k:`redirectColumn`},a),{},{helperText:(0,q.createElement)(q.Fragment,{},`First redirect: `,n.r)})]}}});if(!c)return;let{close:l}=A({title:`Importing...`,Content(){let[t,n]=(0,q.useState)(0),[r,a]=(0,q.useState)();return(0,q.useEffect)(()=>{let t=!1;return setTimeout(b(function*(){if(t)return;let r=0,o=0,d=c.skipFirstLines,f=i.length-d,m=0;try{for(let e of i){var h;if(t)return;if(d){d--;continue}let i=u(e,c);a(i),n(m++/f),yield B(`add_account`,{username:i.u,belongs:(h=i.g)==null?void 0:h.split(`,`),redirect:i.r,overwrite:c.overwriteExistingAccounts}).then(()=>{if(i.p)return p(i.u,i.p)}).catch(e=>{if(e.code===409)return o++;r++})}}finally{l();let t=m-r-o;g(`Results: `+[s(``,r,` failed`),s(``,t,` succeeded`),s(``,o,` skipped because already present`)].filter(Boolean).join(`, `),!t&&r?`error`:r||o?`warning`:`success`),e==null||e()}})),()=>{t=!0}},[]),(0,q.createElement)(F,{sx:{display:`flex`,gap:2,alignItems:`center`}},(0,q.createElement)(he,{icon:G,progress:t}),r==null?void 0:r.u)}});function u(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`})}),Q.apply(this,arguments)}u();var Ae=` `,$=e=>e==null?void 0:e.split(Ae).at(-1);function je(){let{username:e,accountsAsTree:t}=R(),{data:o,reload:s,element:l}=te(`get_accounts`),[u,d]=(0,q.useState)([]),p=Array.isArray(u);(0,q.useEffect)(()=>{Array.isArray(o==null?void 0:o.list)&&p&&d(u.filter(e=>o.list.find(t=>(t==null?void 0:t.username)===$(e))))},[o]);let h=(0,q.useMemo)(()=>o&&J.default.sortBy(o.list,[e=>!e.isGroup,e=>!e.adminActualAccess,`username`]),[o]),v=p&&J.default.find(h,{username:$(u[0])}),y=m(`md`),C=!(u.length>0)||!h?null:p&&u.length>1?(0,q.createElement)(q.Fragment,{},(0,q.createElement)(ee,{},(0,q.createElement)(P,{variant:`h6`},u.length+` selected`),(0,q.createElement)(W,{onClick:N,icon:K},`Remove`)),(0,q.createElement)(_,{},J.default.uniq(u.map($)).map(e=>(0,q.createElement)(a,{key:e},(0,q.createElement)(oe,{},e))))):ue(v||O(),e=>(0,q.createElement)(De,{account:e,groups:h.filter(e=>e.isGroup).map(e=>e.username),addToBar:y&&[(0,q.createElement)(F,{sx:{flex:1}}),Me(e,{fontSize:`large`,sx:{p:1}}),(0,q.createElement)(c,{icon:pe,title:`Close`,onClick:j})],reload:s,done(e,t){d(y?[e]:[]),s(),S(``,t)}}));(0,q.useEffect)(()=>{if(y||!C||!u.length)return;let{close:e}=A({title:J.default.isString(u)?J.default.startCase(u):u.length>1?`Multiple selection`:v?(v.isGroup?`Group: `:`User: `)+v.username:`?`,Content:()=>C,onClose(e){e||j()}});return()=>void e(!0)},[y,u,v]);let w={height:`100%`,display:`flex`,flexDirection:`column`,overflow:`auto`},[E,D]=T(`Show tree`,`Show list`,()=>({icon:ne}),t);return H.accountsAsTree=E,l||(0,q.createElement)(M,{container:!0,sx:{rowSpacing:1,columnSpacing:2,top:0,flex:`1 1 auto`,height:0}},(0,q.createElement)(M,{size:{xs:12,md:5,lg:4,xl:5},sx:w},(0,q.createElement)(F,{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)(i,{variant:`contained`,startIcon:(0,q.createElement)(Se),items:[{children:`user`,onClick:()=>d(`new-user`)},{children:`group`,onClick:()=>d(`new-group`)},{children:`from CSV`,onClick:()=>ke(s)}]},`Add`),x(s),D,(h==null?void 0:h.length)>0&&(0,q.createElement)(P,{sx:{p:1}},`${h.length} account(s)`)),!(h!=null&&h.length)&&(0,q.createElement)(U,{severity:`info`},ge`To access administration <u>remotely</u> you will need to create a user account with admin permission`),(0,q.createElement)(r,{multiSelect:!0,sx:{pr:4,pb:2,minWidth:`15em`},selectedItems:p?u:[],slots:{collapseIcon:Te,expandIcon:Ce},onSelectedItemsChange(e,t){var n,r;!(e==null||(n=e.target)==null||(r=n.closest)==null)&&r.call(n,`.MuiTreeItem-iconContainer`)||d(t)}},h&&(function e(t,r=``){return t.map(t=>{var i,a;return(0,q.createElement)(n,{key:t.username,itemId:r+t.username,label:(0,q.createElement)(F,{sx:{display:`flex`,flexWrap:`wrap`,padding:`.2em 0`,columnGap:`.5em`,alignItems:`center`}},Me(t),(t.disabled||t.canLogin===!1)&&z(we,t.disabled?`Disabled`:`Disabled by its groups`,t.disabled?void 0:{color:`text.secondary`}),(t.expire||t.days_to_live)&&(0,q.createElement)(be),t.adminActualAccess&&z(_e,`Can login into Admin`),t.username,!!((i=t.belongs)!=null&&i.length)&&(0,q.createElement)(F,{sx:{color:`text.secondary`,fontSize:`small`}},`(`,(a=t.belongs)==null?void 0:a.join(`, `),`)`))},E&&e(h.filter(e=>{var n;return(n=t.directMembers)==null?void 0:n.includes(e.username)}),r+t.username+Ae))})})(E?h.filter(e=>!h.some(t=>{var n;return(n=t.members)==null?void 0:n.includes(e.username)})):h))),y&&C&&(0,q.createElement)(M,{size:`grow`,sx:f(f({},w),{},{maxWidth:`100%`})},(0,q.createElement)(fe,{sx:{overflow:`initial`}},(0,q.createElement)(le,{},C))));function O(){return{username:``,hasPassword:u===`new-user`,adminActualAccess:!1,invalidated:void 0,canLogin:!0,canChangePassword:!0,isGroup:u===`new-group`,members:[],directMembers:[]}}function j(){d([])}function N(){return I.apply(this,arguments)}function I(){return I=b(function*(){if(typeof u==`string`||u.some(t=>$(t)===e)&&!(yield k(`You cannot ask to delete the account you are using. Continue with the rest?`)))return;let t=J.default.without(J.default.uniq(u.map($)),e);if(!t.length)return g(`Nothing to delete`,`info`);if(!(yield k(`Delete ${t.length} item(s)?`)))return;let n=[];for(let e of t)(yield B(`del_account`,{username:e}).then(()=>1,()=>0))||n.push(e);if(s(),n.length)return g(`The following items couldn't be deleted: `+n.join(`, `),`error`)}),I.apply(this,arguments)}}function Me(e,t={}){return(0,q.createElement)(e.isGroup?G:ye,t)}export{je as default};
@@ -1,2 +0,0 @@
1
- import{i as e}from"./rolldown-runtime-CacB07dV.js";import{n as t,t as n}from"./DataTable-D_TeYxvk.js";import{t as r}from"./DateTimeField-CsIdkTqz.js";import{Aa as i,C as a,Da as o,Ea as s,F as c,Gr as l,I as u,Kr as d,Ma as f,Pa as p,S as m,St as h,Ta as g,W as _,Wn as v,Yn as y,ai as b,b as ee,ct as x,dt as S,fr as C,ft as w,ii as T,ja as E,un as D,ut as O,wn as te}from"./index-DwXlUHAs.js";import{F as ne,I as re,L as ie,P as ae,q as oe,rt as se}from"./icons-DJJYoK9E.js";var k=e(p()),A=e(C());o(),i();var j=[`label`,`helperText`,`fields`,`value`,`onChange`,`onError`,`setApi`,`reorder`,`prepend`,`noRows`,`valuesForAdd`,`autoRowHeight`,`dialog`,`form`,`details`,`objectK`,`saveOn`,`applyButton`,`height`,`error`,`sx`],M=[`$width`,`$column`,`$type`,`$hideUnder`,`showIf`,`$render`,`$mergeRender`];function N(e){let{label:r,helperText:i,fields:o,value:u,onChange:p,onError:h,setApi:x,reorder:C,prepend:w,noRows:T,valuesForAdd:N,autoRowHeight:I,dialog:L,form:R,details:z,objectK:B,saveOn:V,applyButton:H,height:U,error:W,sx:G}=e,ce=g(e,j),K=Array.isArray(u)?u:!B||!u?[]:Object.entries(u).map(([e,t])=>s({[B]:e},t)),q=(0,k.useMemo)(()=>K.map((e,t)=>v(s({},e),e.hasOwnProperty(`id`)?{$idx:t}:{id:t})),[JSON.stringify(K)]),J=!!W||void 0,Y=e=>t=>s(s({fields:D(o,t).map(e=>{var n;let{$width:r,$column:i,$type:a,$hideUnder:o,showIf:s,$render:c,$mergeRender:l}=e,u=g(e,M);return(!s||s(t))&&A.default.defaults(u,(n=F[a])==null?void 0:n.field)})},R),e);x==null||x({isEqual:te});let[X,le]=(0,k.useState)();return(0,k.createElement)(k.Fragment,{},(0,k.createElement)(m,{rowGap:0,flexWrap:`wrap`,ml:`2px`},r&&(0,k.createElement)(l,{error:J,sx:{color:J?void 0:`text.primary`}},r),i&&(0,k.createElement)(d,{error:J},i)),(0,k.createElement)(b,s(s({},ce),{},{sx:S({height:U},G)}),(0,k.createElement)(n,s(s(s({rows:q,details:z},I&&{getRowHeight:()=>`auto`}),!c(`sm`)&&{compact:!0}),{},{sx:s({".MuiDataGrid-virtualScroller":{minHeight:`3em`}},I&&{".MuiDataGrid-cell":{minHeight:`52px !important`}}),hideFooterSelectedRowCount:!0,hideFooter:!0,slots:{noRowsOverlay:()=>(0,k.createElement)(ee,{},T||`No entries`)},slotProps:{pagination:{showFirstButton:!0,showLastButton:!0}},onCellDoubleClick({field:e,row:t},n){var r;let i=D(o,!1).find(t=>!(0,k.isValidElement)(t)&&t.k===e&&t.$type===`boolean`);if(!i)return;let a=(r=t.$idx)==null?t.id:r,c=t[e],l=!(i.toField||A.default.identity)(c);Z(a,s(s({},K[a]),{},{[e]:(i.fromField||A.default.identity)(l,{originalValue:c})}),n)},columns:[...D(o,!1).map(e=>{var t,n;if((0,k.isValidElement)(e))return;let r=(t=F[e.$type])==null?void 0:t.column;return A.default.defaults(s(s({field:e.k,headerName:(n=e.headerName)==null?typeof e.label==`string`?e.label:O(e.k):n,disableColumnMenu:!0,valueGetter:t=>(e.toField||A.default.identity)(t)},e.$width?{[e.$width>=8?`width`:`flex`]:e.$width}:!(r!=null&&r.width)&&!(r!=null&&r.flex)&&{flex:1}),{},{renderCell:e.$render,mergeRender:e.$mergeRender,hideUnder:e.$hideUnder},e.$column),r)}).filter(Boolean),{field:``,type:`actions`,width:90,headerAlign:`center`,renderHeader(){return(0,k.createElement)(k.Fragment,{},(0,k.createElement)(a,{icon:ie,title:`Add`,size:`small`,onClick(e){return f(function*(){let t=yield _({form:Y({}),title:`Add`,values:N,dialogProps:L});if(!t)return;let n=K.slice()||[];w?n.unshift(t):n.push(t),Q(n,e)})()}}),X!==void 0&&(0,k.createElement)(a,{icon:ae,title:`Undo`,size:`small`,onClick:e=>Q(X,e)}))},getActions({row:e}){let{$idx:n=e.id}=e,i=`Modify`;return[(0,k.createElement)(t,{key:`edit`,icon:(0,k.createElement)(oe),label:i,title:i,onClick(t){return f(function*(){t.stopPropagation();let a=yield _({values:e,form:(e,{submit:t})=>Y(V===`change`&&{save:!1,onChange(e){Z(n,e,`change`)}}||H&&{addToBar:(0,k.createElement)(P,s(s({children:`Apply`,sx:s({ml:2},H.sx)},H),{},{onClick(){t(function(){var e=f(function*(e){let t=Z(n,s(s({},K[n]),e),`save`);yield H.onClick(t)});return function(t){return e.apply(this,arguments)}}())}}))}||V&&{save:!1})(e),title:(0,k.createElement)(k.Fragment,{},i,r&&` - `,r),dialogProps:L});a&&Z(n,a,t)})()}}),(0,k.createElement)(t,{key:`delete`,icon:(0,k.createElement)(se),label:`Delete`,showInMenu:C,onClick(e){e.stopPropagation(),Q(K.filter((e,t)=>t!==n),e)}}),C&&n&&(0,k.createElement)(t,{key:`up`,icon:(0,k.createElement)(re),label:`Move up`,showInMenu:!0,onClick(e){e.stopPropagation(),Q(y(K.slice(),n,n-1),e)}}),C&&n<q.length-1&&(0,k.createElement)(t,{key:`down`,icon:(0,k.createElement)(ne),label:`Move down`,showInMenu:!0,onClick(e){e.stopPropagation(),Q(y(K.slice(),n,n+1),e)}})].filter(Boolean)}}]}))));function Z(e,t,n){return Q(K.map((n,r)=>r===e?t:n),n)}function Q(e,t){let n=$(e);return p(n,{was:u,event:t}),V!==`change`&&le(K),n}function $(e){return B?Object.fromEntries(e.map(e=>{let{[B]:t}=e;return[t,g(e,[B].map(E))]})):e}}function P(e){return(0,k.createElement)(T,s({ref:u([`s`]).ref,title:`Apply
2
- (ctrl+s)`},e))}var F={boolean:{field:{comp:w},column:{renderCell:({value:e})=>e&&(0,k.createElement)(x)}},dateTime:{field:{comp:r},column:{minWidth:96,flex:.5,renderCell:({value:e})=>h(e)}}};export{N as t};
@@ -1,4 +0,0 @@
1
- import{i as e}from"./rolldown-runtime-CacB07dV.js";import{Bn as t,C as n,Jt as r,N as i,Nt as a,Pa as o,S as s,_ as c,ai as l,jt as u,lr as d,pt as f,qt as p,tt as m,vi as h,vt as g,y as _}from"./index-DwXlUHAs.js";import{K as v,q as y}from"./icons-DJJYoK9E.js";import{t as b}from"./TextEditor-Cr0VI9Xi.js";var x=e(o());function S(){m.title=`Config file`;let{data:e,reload:o,element:S}=c(`get_config_text`,{}),[C,w]=(0,x.useState)(),[T,E]=(0,x.useState)(),[D,O]=(0,x.useState)(!1);return(0,x.useEffect)(()=>{E(e==null?void 0:e.text),w(e==null?void 0:e.text)},[e]),S||(0,x.createElement)(x.Fragment,{},(0,x.createElement)(s,{flexWrap:`wrap`,justifyContent:`space-between`},(0,x.createElement)(_,{icon:v,onClick:A,disabled:!e},`Export without passwords`),D?(0,x.createElement)(x.Fragment,{},i(o),(0,x.createElement)(n,{icon:g,title:`Save
2
- (ctrl+enter)`,modified:C!==T,onClick:k}),(0,x.createElement)(h,{severity:`warning`,sx:{minWidth:`10em`}},`Be careful, you can easily break things here`)):(0,x.createElement)(_,{icon:y,variant:`outlined`,onClick(){O(!0),setTimeout(()=>a(`main textarea`),500)}},`Edit`),(0,x.createElement)(l,{sx:{flex:1,minWidth:`fit-content`}},(0,x.createElement)(f,{label:`File path`,value:e==null?void 0:e.fullPath,size:`small`}))),S||C!==void 0&&(0,x.createElement)(l,{sx:{"& pre,& textarea":{wordBreak:`break-all !important`}}},(0,x.createElement)(p,{margin:10},(0,x.createElement)(b,{value:C,disabled:!D,style:{background:`#8881`},onValueChange:w,onKeyDown(e){[`s`,`Enter`].includes(r(e))&&(k(),e.preventDefault())}}))));function k(){return d(`set_config_text`,{text:C}).then(()=>E(C))}function A(){var n;let r=(C||``).replace(/^(\s*(\w*password|_\w*|token|srp|dynamic_dns_url):\s*).+(?:\r?\n|$)/gm,`$1removed
3
- `).replace(/(:\/\/)[^/@\s]+@/g,`$1removed@`)+t(`custom_html: | # this is currently ignored by hfs, just here for reference
4
- `,(n=e.customHtml)==null?void 0:n.replace(/^/gm,` `));r&&u(`config_no_passwords.yaml`,r)}}export{S as default};
@@ -1,2 +0,0 @@
1
- import{i as e}from"./rolldown-runtime-CacB07dV.js";import{A as t,Bn as n,C as r,Cn as i,Da as a,Ea as o,H as s,I as c,Ma as l,N as u,Pa as d,_ as f,ai as p,fr as m,gt as h,ir as g,it as _,lr as v,na as y,or as b,tt as x,vi as S,vt as C,xr as w,z as T,zt as E}from"./index-DwXlUHAs.js";import{t as D}from"./TextEditor-Cr0VI9Xi.js";var O=e(m()),k=e(d());a();function A(e,t,n){return(0,k.createElement)(j,o({checked:e||!1,onChange(e,n){t(n)},disabled:e===void 0},n))}var j=y(w)(({theme:e,size:t})=>{let n=t&&t===`small`;return{height:n?36:48,width:n?18:26,padding:5,"& .MuiSwitch-switchBase":{margin:1,padding:0,transform:`translateY(${n?16:22}px)`,"&.Mui-checked":{color:e.palette.primary.contrastText,transform:`translateY(2px)`,"& .MuiSwitch-thumb:before":{backgroundImage:`url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" height="${n?14:20}" width="20" viewBox="0 0 20 20"><path fill="${encodeURIComponent(e.palette.primary.contrastText)}" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"/></svg>')`},"& + .MuiSwitch-track":{backgroundColor:e.palette.grey[500]}}},"& .Mui-checked .MuiSwitch-thumb":{backgroundColor:e.palette.primary.main,boxShadow:`0px 2px 1px 1px rgba(0, 0, 0, 0.2), 0px 1px 1px 0px rgba(0, 0, 0, 0.14), 0px 1px 3px 0px rgba(0, 0, 0, 0.12)`},"& .MuiSwitch-thumb":{width:n?16:24,height:n?16:24,backgroundColor:e.palette.grey[700],"&::before":{content:`''`,position:`absolute`,width:`85%`,height:`85%`,left:0,top:0,backgroundRepeat:`no-repeat`,backgroundPosition:`center`,backgroundImage:`url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" height="${n?14:20}" width="20" viewBox="0 0 20 20"><path fill="${encodeURIComponent(e.palette.primary.contrastText)}" d="M19,13H5V11H19V13Z"/></svg>')`}},"& .MuiSwitch-track":{backgroundColor:e.palette.grey[500],borderRadius:20/2}}});a();var M={top:`Top of HTML Body`,bottom:`Bottom of HTML Body`};function N({setTitleSide:e}){let{data:a,reload:d,element:m}=f(`get_custom_html`),{customHtmlSection:y}=_(),[w,j]=(0,k.useState)({}),[N,P]=(0,k.useState)({});(0,k.useEffect)(()=>{a&&(P(a.sections),j(a.sections),R(a.enabled))},[a]);let F=(0,k.useMemo)(()=>{let e=O.default.sortBy(Object.keys(w),i);return e.length&&!e.includes(y)&&(x.customHtmlSection=O.default.findKey(w,Boolean)||(e==null?void 0:e[0])||``),e.map(e=>{var t;return{value:e,label:(M[e]||n(`HTTP `,b[e])||O.default.startCase(e))+((t=w[e])!=null&&t.trim()?` *`:``)}})},[w]),I=(0,k.useMemo)(()=>!O.default.isEqualWith(N,w,(e,t)=>!e&&!t||void 0),[N,w]),[L,R]=(0,k.useState)();e((0,k.useMemo)(()=>(0,k.createElement)(p,{sx:{display:{xs:`none`,md:`block`}}},(0,k.createElement)(S,{severity:`info`},E("Add HTML code to some parts of the Front-end. It's saved to file `custom.html`, that you can edit directly with your editor of choice. "),T(`customization`,`More help`)),(0,k.createElement)(S,{severity:`info`},`To customize icons `,T(`customization#icons`,`read documentation`))),[]));let z=c([`s`,`Enter`]);return m||(0,k.createElement)(k.Fragment,{},(0,k.createElement)(p,{sx:{display:`flex`,alignItems:`center`,gap:1,mb:1}},(0,k.createElement)(h,{label:`Section`,value:y,options:F,onChange:e=>x.customHtmlSection=e}),u(d),(0,k.createElement)(r,{ref:z.ref,icon:C,title:`Save
2
- (ctrl+s)`,modified:I,doneAnimation:!0,onClick:B}),t(`Enable all sections`,void 0,A(L,function(){var e=l(function*(e){try{yield v(`set_config`,{values:{[g.disable_custom_html]:!e}}),R(e)}catch(e){s(e)}});return function(t){return e.apply(this,arguments)}}()))),(0,k.createElement)(D,{lang:y===`style`?`css`:y===`script`?`js`:`html`,value:(w==null?void 0:w[y])||``,style:{background:`#8881`},onValueChange(e){j(t=>o(o({},t),{},{[y]:e}))}}));function B(){return v(`set_custom_html`,{sections:w}).then(()=>P(w))}}export{N as default};