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/adminApis.js CHANGED
@@ -150,11 +150,11 @@ exports.adminApis = {
150
150
  configFile: config_1.configFile.getPath(),
151
151
  urls: await (0, listen_1.getUrls)(),
152
152
  ips: await (0, listen_1.getIps)(false),
153
- connectionAddress: (0, connections_1.normalizeIp)(ctx.socket.localAddress || ''),
153
+ connectionAddress: (0, misc_1.normalizeIp)(ctx.socket.localAddress || ''),
154
154
  baseUrl: await (0, listen_1.getBaseUrlOrDefault)(),
155
155
  roots: roots_1.roots.get(),
156
156
  anyAccountCanLoginAdmin: anyAccountCanLoginAdmin(),
157
- updatePossible: !await (0, update_1.updateSupported)() ? false : (await (0, update_1.localUpdateAvailable)()) ? 'local' : true,
157
+ updatePossible: !await (0, update_1.updateNotSupported)() && (await (0, update_1.localUpdateAvailable)() ? 'local' : true),
158
158
  previousVersionAvailable: await (0, update_1.previousAvailable)(),
159
159
  autoCheckUpdateResult: update_1.autoCheckUpdateResult.get(), // in this form, we get the same type of the serialized json
160
160
  alerts: // in this form, we get the same type of the serialized json
@@ -0,0 +1,51 @@
1
+ "use strict";
2
+ // This file is part of HFS - Copyright 2026, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt
3
+ var __importDefault = (this && this.__importDefault) || function (mod) {
4
+ return (mod && mod.__esModule) ? mod : { "default": mod };
5
+ };
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.adminLang = void 0;
8
+ exports.getAdminLangs = getAdminLangs;
9
+ exports.getAdminLangData = getAdminLangData;
10
+ exports.invalidateAdminLang = invalidateAdminLang;
11
+ const promises_1 = require("fs/promises");
12
+ const misc_1 = require("./misc");
13
+ const config_1 = require("./config");
14
+ const expiringCache_1 = require("./expiringCache");
15
+ const embedded_1 = __importDefault(require("./admin-langs/embedded"));
16
+ const const_1 = require("./const");
17
+ const lang_1 = require("./lang");
18
+ const fast_glob_1 = __importDefault(require("fast-glob"));
19
+ const lodash_1 = __importDefault(require("lodash"));
20
+ async function getAdminLangs() {
21
+ const files = await (0, fast_glob_1.default)((0, lang_1.code2file)('*', true));
22
+ return lodash_1.default.uniq([...Object.keys(embedded_1.default), ...files.map(name => (0, lang_1.file2code)(name, true))
23
+ .filter(code => (0, lang_1.normalizeLangCode)(code) === code)]);
24
+ }
25
+ exports.adminLang = (0, config_1.defineConfig)(misc_1.CFG.admin_lang, '', v => (0, lang_1.langCodeForLookup)((0, lang_1.normalizeLangCode)(v)));
26
+ const cache = (0, expiringCache_1.expiringCache)(3_000);
27
+ function getAdminLangData(ctx, langs) {
28
+ const configured = exports.adminLang.compiled();
29
+ const code = langs.includes(configured) ? configured : browserAdminLang(ctx, langs);
30
+ return cache.try(code, async () => ({
31
+ [code]: (0, misc_1.tryJson)(await (0, promises_1.readFile)((0, lang_1.code2file)(code, true), 'utf8').catch(() => ''))
32
+ || embedded_1.default[code] || embedded_1.default.en,
33
+ ...code === const_1.EMBEDDED_LANGUAGE ? {} : { [const_1.EMBEDDED_LANGUAGE]: embedded_1.default.en },
34
+ }));
35
+ }
36
+ function invalidateAdminLang(code) {
37
+ cache.delete(code); // uploads and deletions must be visible immediately after the Admin reloads
38
+ }
39
+ function browserAdminLang(ctx, langs) {
40
+ const accepted = ctx?.get('Accept-Language') || '';
41
+ for (const raw of accepted.split(',')) {
42
+ let code = (0, lang_1.normalizeLangCode)(raw);
43
+ while (code) {
44
+ const candidate = (0, lang_1.langCodeForLookup)(code);
45
+ if (langs.includes(candidate))
46
+ return candidate;
47
+ code = code.substring(0, code.lastIndexOf('-'));
48
+ }
49
+ }
50
+ return const_1.EMBEDDED_LANGUAGE;
51
+ }
package/src/api.auth.js CHANGED
@@ -67,7 +67,7 @@ exports.authApis = {
67
67
  return new apiMiddleware_1.ApiError(const_1.HTTP_SERVER_ERROR);
68
68
  if (account?.plugin?.auth) // tell client to do clear-text login, before firing attemptingLogin, before triggering anti-brute
69
69
  return new apiMiddleware_1.ApiError(const_1.HTTP_METHOD_NOT_ALLOWED);
70
- if ((await events_1.default.emitAsync('attemptingLogin', { ctx, username }))?.isDefaultPrevented())
70
+ if ((await events_1.default.emitAsync('attemptingLogin', { ctx, username, via: 'srp' }))?.isDefaultPrevented())
71
71
  return;
72
72
  if (account && !(0, perm_1.accountCanLogin)(account)) {
73
73
  ctx.logExtra({ u: username });
package/src/api.lang.js CHANGED
@@ -12,30 +12,36 @@ const const_1 = require("./const");
12
12
  const misc_1 = require("./misc");
13
13
  const lang_1 = require("./lang");
14
14
  const embedded_1 = __importDefault(require("./langs/embedded"));
15
+ const embedded_2 = __importDefault(require("./admin-langs/embedded"));
16
+ const adminLang_1 = require("./adminLang");
15
17
  const SendList_1 = require("./SendList");
16
18
  const apis = {
17
19
  get_langs() {
18
20
  return new SendList_1.SendListReadable({
19
21
  doAtStart: async (list) => {
20
- for await (let name of fast_glob_1.default.stream((0, lang_1.code2file)('*'))) {
21
- name = String(name);
22
- const code = (0, lang_1.file2code)(name);
23
- try {
24
- const data = JSON.parse(await (0, promises_1.readFile)(name, 'utf8'));
25
- list.add({ code, ...lodash_1.default.omit(data, 'translate') });
22
+ for (const admin of [false, true]) {
23
+ for await (const name of fast_glob_1.default.stream((0, lang_1.code2file)('*', admin))) {
24
+ const code = (0, lang_1.file2code)(String(name), admin);
25
+ try {
26
+ const data = JSON.parse(await (0, promises_1.readFile)(String(name), 'utf8'));
27
+ list.add({ ...lodash_1.default.omit(data, 'translate'), code, admin, embedded: false });
28
+ }
29
+ catch { }
26
30
  }
27
- catch { }
31
+ for (const [code, data] of Object.entries(admin ? embedded_2.default : embedded_1.default))
32
+ list.add({ ...lodash_1.default.omit(data, 'translate'), code, admin, embedded: true });
28
33
  }
29
- for (const [code, data] of Object.entries(embedded_1.default))
30
- list.add({ code, embedded: true, ...lodash_1.default.omit(data, 'translate') });
31
34
  list.close();
32
35
  }
33
36
  });
34
37
  },
35
- async del_lang({ code }) {
38
+ async del_lang({ code, admin = false }) {
39
+ (0, misc_1.apiAssertTypes)({ string: { code }, boolean: { admin } });
36
40
  validateCode(code);
37
41
  try {
38
- await (0, promises_1.rm)((0, lang_1.code2file)(code));
42
+ await (0, promises_1.rm)((0, lang_1.code2file)(code, admin));
43
+ if (admin)
44
+ (0, adminLang_1.invalidateAdminLang)(code.toLowerCase());
39
45
  return {};
40
46
  }
41
47
  catch (e) {
@@ -45,14 +51,17 @@ const apis = {
45
51
  async add_langs({ langs }) {
46
52
  (0, misc_1.apiAssertTypes)({ object: { langs } });
47
53
  for (let [code, content] of Object.entries(langs)) {
48
- code = (0, lang_1.file2code)(code);
54
+ const admin = code.startsWith('hfs-admin-lang-');
55
+ code = (0, lang_1.file2code)(code, admin);
49
56
  validateCode(code);
50
- const fn = (0, lang_1.code2file)(code);
57
+ const fn = (0, lang_1.code2file)(code, admin);
51
58
  const s = String(content);
52
59
  const o = (0, misc_1.tryJson)(s);
53
60
  if (!o?.translate)
54
61
  return new apiMiddleware_1.ApiError(const_1.HTTP_NOT_ACCEPTABLE, "bad content for file " + fn);
55
62
  await (0, promises_1.writeFile)(fn, s, 'utf8');
63
+ if (admin)
64
+ (0, adminLang_1.invalidateAdminLang)(code.toLowerCase());
56
65
  }
57
66
  return {};
58
67
  }
package/src/api.log.js CHANGED
@@ -33,25 +33,27 @@ exports.default = {
33
33
  await (0, serveFile_1.serveFile)(ctx, log.path);
34
34
  return null;
35
35
  },
36
- get_log({ file = 'log' }, ctx) {
36
+ get_log({ file = 'log', skipInitial }, ctx) {
37
37
  (0, misc_1.apiAssertTypes)({ string: { file } });
38
38
  const files = file.split('|'); // potentially more than one
39
39
  return new SendList_1.SendListReadable({
40
40
  bufferTime: 10,
41
41
  async doAtStart(list) {
42
42
  if (file === 'disconnections') {
43
- for (const x of connections_1.disconnectionsLog)
43
+ for (const x of skipInitial ? [] : connections_1.disconnectionsLog)
44
44
  list.add(x);
45
45
  ctx.res.once('close', events_1.default.on('disconnection', x => list.add(x)));
46
46
  return list.ready();
47
47
  }
48
48
  if (file === 'ips') {
49
+ if (skipInitial)
50
+ return list.ready();
49
51
  for await (const [k, v] of ips_1.ips.iterator())
50
52
  list.add({ ip: k, ...v });
51
53
  return list.ready();
52
54
  }
53
55
  if (file === 'console') {
54
- for (const chunk of lodash_1.default.chunk(consoleLog_1.consoleLog, 1000)) { // avoid occupying the thread too long
56
+ for (const chunk of lodash_1.default.chunk(skipInitial ? [] : consoleLog_1.consoleLog, 1000)) { // avoid occupying the thread too long
55
57
  for (const x of chunk)
56
58
  list.add(x);
57
59
  await (0, cross_1.wait)(0);
package/src/api.net.js CHANGED
@@ -14,6 +14,7 @@ const promises_1 = require("dns/promises");
14
14
  const net_1 = require("net");
15
15
  const nat_1 = require("./nat");
16
16
  const acme_1 = require("./acme");
17
+ const acmeConfig_1 = require("./acmeConfig");
17
18
  const selfCheck_1 = require("./selfCheck");
18
19
  exports.default = {
19
20
  get_nat: nat_1.getNatInfo,
@@ -83,13 +84,29 @@ exports.default = {
83
84
  const results = (0, misc_1.onlyTruthy)(await (0, misc_1.promiseBestEffort)(publicIps.map(ip => (0, selfCheck_1.selfCheck)(`${proto}://${(0, misc_1.ipForUrl)(ip)}${finalPort === defPort ? '' : ':' + finalPort}`))));
84
85
  return results.length ? results : new apiMiddleware_1.ApiError(const_1.HTTP_SERVICE_UNAVAILABLE);
85
86
  },
86
- async make_cert({ domain, email, altNames }) {
87
- (0, misc_1.apiAssertTypes)({ string: { domain }, string_undefined: { email }, array_undefined: { altNames } });
87
+ get_acme: acmeConfig_1.getAcmeSettings,
88
+ get_acme_status: acme_1.getAcmeStatusEvents,
89
+ async set_acme({ settings }) {
90
+ if ((0, acme_1.getAcmeStatus)().state === 'running')
91
+ throw new apiMiddleware_1.ApiError(409, "Certificate request already running");
92
+ if (!settings || typeof settings !== 'object')
93
+ throw new apiMiddleware_1.ApiError(const_1.HTTP_BAD_REQUEST);
94
+ return (0, acmeConfig_1.setAcmeSettings)(settings).catch(error => { throw new apiMiddleware_1.ApiError(const_1.HTTP_BAD_REQUEST, error.message); });
95
+ },
96
+ async make_cert({ domain, email, altNames, background }) {
97
+ (0, misc_1.apiAssertTypes)({ string: { domain }, string_undefined: { email }, array_undefined: { altNames }, boolean_undefined: { background } });
88
98
  if (!domain)
89
99
  return new apiMiddleware_1.ApiError(const_1.HTTP_BAD_REQUEST, 'bad params');
90
100
  if (altNames?.some((name) => typeof name !== 'string'))
91
101
  return new apiMiddleware_1.ApiError(const_1.HTTP_BAD_REQUEST, 'bad altNames');
92
- await (0, acme_1.makeCert)(domain, email, altNames).catch(e => {
102
+ if ((0, acme_1.getAcmeStatus)().state === 'running')
103
+ throw new apiMiddleware_1.ApiError(409, "Certificate request already running");
104
+ const pending = (0, acme_1.makeCert)(domain, email, altNames);
105
+ if (background) {
106
+ void pending.catch(() => { }); // the status stream carries the error even after the browser disconnects
107
+ return { id: (0, acme_1.getAcmeStatus)().id };
108
+ }
109
+ await pending.catch(e => {
93
110
  throw new apiMiddleware_1.ApiError(const_1.HTTP_SERVER_ERROR, e.message || String(e));
94
111
  });
95
112
  return {};
@@ -13,9 +13,10 @@ const promises_1 = require("fs/promises");
13
13
  const github_1 = require("./github");
14
14
  const const_1 = require("./const");
15
15
  const SendList_1 = require("./SendList");
16
+ const pluginCatalog_1 = require("./pluginCatalog");
16
17
  const apis = {
17
- get_plugins({}, ctx) {
18
- const list = new SendList_1.SendListReadable({ addAtStart: [...(0, plugins_1.mapPlugins)(serialize, false), ...(0, plugins_1.getInactivePlugins)().map(serialize)] });
18
+ get_plugins({ skipInitial }, ctx) {
19
+ const list = new SendList_1.SendListReadable({ addAtStart: skipInitial ? [] : [...(0, plugins_1.mapPlugins)(serialize, false), ...(0, plugins_1.getInactivePlugins)().map(serialize)] });
19
20
  return list.events(ctx, {
20
21
  pluginInstalled: p => list.add(serialize(p)),
21
22
  'pluginStarted pluginStopped pluginUpdated': p => {
@@ -26,7 +27,7 @@ const apis = {
26
27
  pluginLog: id => list.update({ id }, { log: true }) // SendList is already capping frequency
27
28
  });
28
29
  },
29
- async get_plugin_updates({}, ctx) {
30
+ async get_plugin_updates({ skipInitial }, ctx) {
30
31
  return new SendList_1.SendListReadable({
31
32
  async doAtStart(list) {
32
33
  const errs = {};
@@ -38,6 +39,8 @@ const apis = {
38
39
  list.update({ id }, { updated: true });
39
40
  }
40
41
  });
42
+ if (skipInitial)
43
+ return list.ready();
41
44
  await plugins_1.pluginsScanned;
42
45
  await Promise.allSettled(lodash_1.default.map((0, github_1.getFolder2repo)(), async (repo, folder) => {
43
46
  try {
@@ -73,11 +76,17 @@ const apis = {
73
76
  async start_plugin({ id }) {
74
77
  assertPluginId(id);
75
78
  if ((0, plugins_1.isPluginRunning)(id))
76
- return { msg: 'already running' };
79
+ return serializeRunningPlugin(id);
77
80
  if (plugins_1.suspendPlugins.get())
78
81
  return new apiMiddleware_1.ApiError(misc_1.HTTP_PRECONDITION_FAILED, 'all plugins suspended');
79
82
  await (0, plugins_1.stopPlugin)(id);
80
- return (0, plugins_1.startPlugin)(id).then(() => ({}), e => new apiMiddleware_1.ApiError(const_1.HTTP_SERVER_ERROR, e.message));
83
+ try {
84
+ await (0, plugins_1.startPlugin)(id);
85
+ }
86
+ catch (e) {
87
+ return new apiMiddleware_1.ApiError(const_1.HTTP_SERVER_ERROR, e.message);
88
+ }
89
+ return serializeRunningPlugin(id);
81
90
  },
82
91
  async stop_plugin({ id }) {
83
92
  assertPluginId(id);
@@ -104,42 +113,63 @@ const apis = {
104
113
  }
105
114
  };
106
115
  },
107
- get_online_plugins({ text }, ctx) {
116
+ get_online_plugins({ text, skipInitial }, ctx) {
108
117
  if (text !== undefined && !lodash_1.default.isString(text))
109
118
  return new apiMiddleware_1.ApiError(const_1.HTTP_BAD_REQUEST, 'bad text');
110
119
  return new SendList_1.SendListReadable({
111
120
  async doAtStart(list) {
121
+ // resumed clients retain their own rows; updates for other repos are ignored by useApiList
112
122
  const repos = [];
113
123
  list.events(ctx, {
114
124
  pluginInstalled: p => {
115
- if (repos.includes(p.repo))
125
+ if (skipInitial || repos.includes(p.repo))
116
126
  list.update({ id: p.repo }, { installed: true });
117
127
  },
118
128
  pluginUninstalled: (_folder, repo) => {
119
129
  if (typeof repo !== 'string')
120
130
  return; // custom repo
121
- if (repos.includes(repo))
131
+ if (skipInitial || repos.includes(repo))
122
132
  list.update({ id: repo }, { installed: false });
123
133
  },
124
134
  pluginDownload({ repo, status }) {
125
- if (repos.includes(repo))
135
+ if (skipInitial || repos.includes(repo))
126
136
  list.update({ id: repo }, { downloading: status ?? null });
127
137
  }
128
138
  });
139
+ if (skipInitial)
140
+ return list.ready();
141
+ let updatedAt;
142
+ let previous = new Map();
129
143
  try {
130
- const already = Object.values((0, github_1.getFolder2repo)()).filter(Boolean).map(String);
131
- for await (const pl of await (0, github_1.searchPlugins)(text, { skipRepos: already })) {
144
+ for await (const snapshot of (0, pluginCatalog_1.getPluginCatalog)()) {
145
+ const already = Object.values((0, github_1.getFolder2repo)()).filter(Boolean).map(String);
146
+ const plugins = await (0, pluginCatalog_1.searchPluginCatalog)(snapshot, text, already);
132
147
  if (ctx.isAborted())
133
148
  return;
134
- const repo = pl.repo || pl.id; // .repo property can be more trustworthy in case github user renamed and left the previous link in 'repo'
135
- const missing = (0, plugins_1.getMissingDependencies)(pl);
136
- if (missing.length)
137
- pl.missing = missing;
138
- list.add(pl);
139
- repos.push(repo);
149
+ const rows = plugins.map(pl => {
150
+ const missing = (0, plugins_1.getMissingDependencies)(pl);
151
+ return { ...pl, missing: missing.length ? missing : null, downloading: github_1.downloading[pl.repo] ?? null };
152
+ });
153
+ // reconcile snapshots without dropping selection or retaining obsolete optional metadata
154
+ for (const id of previous.keys())
155
+ if (!rows.some(row => row.id === id))
156
+ list.remove({ id });
157
+ for (const row of rows) {
158
+ const old = previous.get(row.id);
159
+ if (old)
160
+ list.update({ id: row.id }, { ...lodash_1.default.mapValues(old, () => null), ...lodash_1.default.mapValues(row, value => value ?? null) });
161
+ else
162
+ list.add(row);
163
+ }
164
+ previous = new Map(rows.map(row => [row.id, row]));
165
+ repos.splice(0, repos.length, ...rows.map(row => row.repo));
166
+ updatedAt = snapshot.updatedAt;
167
+ list.props({ updatedAt, refreshing: snapshot.refreshing });
168
+ list.ready();
140
169
  }
141
170
  }
142
171
  catch (err) {
172
+ list.props({ updatedAt, refreshing: false });
143
173
  list.error(err.code || err.message);
144
174
  }
145
175
  list.ready();
@@ -177,12 +207,12 @@ const apis = {
177
207
  (0, plugins_1.setPluginConfig)(id, null);
178
208
  return {};
179
209
  },
180
- get_plugin_log({ id }, ctx) {
210
+ get_plugin_log({ id, skipInitial }, ctx) {
181
211
  assertPluginId(id);
182
212
  const p = (0, plugins_1.getPluginInfo)(id);
183
213
  if (!p)
184
214
  return new apiMiddleware_1.ApiError(const_1.HTTP_NOT_FOUND);
185
- const list = new SendList_1.SendListReadable({ addAtStart: p.log });
215
+ const list = new SendList_1.SendListReadable({ addAtStart: skipInitial ? [] : p.log });
186
216
  return list.events(ctx, {
187
217
  ['pluginLog:' + id]: x => list.add(x)
188
218
  });
@@ -202,6 +232,10 @@ function serialize(p) {
202
232
  : JSON.stringify(o.config, (_k, v) => lodash_1.default.isFunction(v) ? String(v) : v); // allow simple functions
203
233
  return lodash_1.default.defaults(o, { started: null, badApi: null }); // nulls should be used to be sure to overwrite previous values,
204
234
  }
235
+ function serializeRunningPlugin(id) {
236
+ const plugin = (0, plugins_1.firstPlugin)((plugin, pluginId) => pluginId === id ? plugin : undefined);
237
+ return plugin ? serialize(plugin) : new apiMiddleware_1.ApiError(const_1.HTTP_SERVER_ERROR);
238
+ }
205
239
  async function checkDependencies(plugin) {
206
240
  const miss = await (0, plugins_1.getMissingDependencies)(plugin);
207
241
  if (miss.length)
package/src/api.vfs.js CHANGED
@@ -36,8 +36,9 @@ exports.default = {
36
36
  delete copyStats.mtime;
37
37
  const inherited = node.parent && (0, vfs_1.permsFromParent)(node.parent, {});
38
38
  const byMasks = node.original && lodash_1.default.pickBy(node, (v, k) => v !== node.original[k] // something is changing me...
39
- && !(inherited && k in inherited) // ...and it's not inheritance...
40
- && misc_1.PERM_KEYS.includes(k)); // ...must be masks. Please limit this to perms
39
+ && (k === 'gui_asset'
40
+ ? v !== node.parent?.gui_asset // gui_asset inherits outside the permission helpers
41
+ : !(inherited && k in inherited) && misc_1.PERM_KEYS.includes(k))); // ...must be masks
41
42
  return {
42
43
  ...copyStats,
43
44
  ...node.original || node,
package/src/argv.js CHANGED
@@ -6,3 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.argv = void 0;
7
7
  const minimist_1 = __importDefault(require("minimist"));
8
8
  exports.argv = (0, minimist_1.default)(process.argv.slice(2));
9
+ // Node's internal flag is read-only when enabled via its CLI, but remains configurable
10
+ Object.defineProperty(process, 'traceProcessWarnings', {
11
+ value: exports.argv['trace-warnings'] !== false, writable: true, configurable: true,
12
+ });
package/src/auth.js CHANGED
@@ -85,13 +85,19 @@ function getCurrentUsername(ctx) {
85
85
  async function clearTextLogin(ctx, u, p, via) {
86
86
  if (!p)
87
87
  return;
88
- if ((await events_1.default.emitAsync('attemptingLogin', { ctx, username: u, via }))?.isDefaultPrevented())
89
- return;
90
- const plugins = await events_1.default.emitAsync('clearTextLogin', { ctx, username: u, password: p, via }); // provide clear password to plugins
91
- const a = plugins?.some(x => x === true) ? (0, perm_1.getAccount)(u) : await srpCheck(u, p);
92
- if (!a && u)
93
- events_1.default.emit('failedLogin', { ctx, username: u, via });
94
- return a;
88
+ let account;
89
+ try {
90
+ if ((await events_1.default.emitAsync('attemptingLogin', { ctx, username: u, via }))?.isDefaultPrevented())
91
+ return;
92
+ const plugins = await events_1.default.emitAsync('clearTextLogin', { ctx, username: u, password: p, via }); // provide clear password to plugins
93
+ account = plugins?.some(x => x === true) ? (0, perm_1.getAccount)(u) : await srpCheck(u, p);
94
+ if (!account && u)
95
+ events_1.default.emit('failedLogin', { ctx, username: u, via });
96
+ return account;
97
+ }
98
+ finally {
99
+ events_1.default.emit('loginAttemptFinished', { ctx, username: u, via, success: Boolean(account) });
100
+ }
95
101
  }
96
102
  // centralized log-in state
97
103
  async function setLoggedIn(ctx, username, via) {
@@ -122,8 +128,6 @@ async function setLoggedIn(ctx, username, via) {
122
128
  ctx.throw(cross_const_1.HTTP_UNAUTHORIZED, error);
123
129
  }
124
130
  const normalized = (0, perm_1.normalizeUsername)(username);
125
- if (s.username !== normalized)
126
- delete s.allowNet; // discard restrictions cached for another identity before replacing the session account
127
131
  s.username = normalized;
128
132
  s.ip = ctx.ip;
129
133
  s.stamp = sessionStamp;
package/src/basicWeb.js CHANGED
@@ -17,8 +17,12 @@ const autoBasic = (0, config_1.defineConfig)(misc_1.CFG.auto_basic, true, v => l
17
17
  function basicWeb(ctx, node) {
18
18
  const { get } = ctx.query;
19
19
  if (get === 'login') {
20
- if ((0, auth_1.getCurrentUsername)(ctx))
21
- ctx.redirect(ctx.get('referer'));
20
+ if ((0, auth_1.getCurrentUsername)(ctx)) {
21
+ const referer = ctx.get('referer');
22
+ const url = referer && (0, misc_1.try_)(() => new URL(referer, ctx.URL));
23
+ // keep the origin in Location because a same-origin path starting with // would redirect externally
24
+ ctx.redirect(url && url.origin === ctx.URL.origin ? url.origin + url.pathname + url.search + url.hash : '/');
25
+ }
22
26
  else {
23
27
  ctx.set('WWW-Authenticate', cross_const_1.BASIC_AUTHENTICATE_HEADER);
24
28
  ctx.status = cross_const_1.HTTP_UNAUTHORIZED;
package/src/block.js CHANGED
@@ -26,7 +26,7 @@ exports.block = (0, config_1.defineConfig)(misc_1.CFG.block, [], rules => {
26
26
  });
27
27
  return ret;
28
28
  });
29
- function applyBlock(socket, ip = (0, connections_1.normalizeIp)(socket.remoteAddress || '')) {
29
+ function applyBlock(socket, ip = (0, misc_1.normalizeIp)(socket.remoteAddress || '')) {
30
30
  if (ip && isBlocked(ip))
31
31
  return (0, connections_1.disconnect)(socket, 'block-ip');
32
32
  }
package/src/config.js CHANGED
@@ -244,6 +244,7 @@ Most common configurations:
244
244
  For a description of each configuration, please refer to https://rejetto.com/hfs-config
245
245
  Other options:
246
246
  --debug will print extra information
247
+ --no-trace-warnings will disable stack traces for Node.js warnings (enabled by default)
247
248
  `);
248
249
  process.exit(0);
249
250
  });
@@ -5,7 +5,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  };
6
6
  Object.defineProperty(exports, "__esModule", { value: true });
7
7
  exports.disconnectionsLog = exports.Connection = void 0;
8
- exports.normalizeIp = normalizeIp;
9
8
  exports.newConnection = newConnection;
10
9
  exports.getConnections = getConnections;
11
10
  exports.socket2connection = socket2connection;
@@ -16,6 +15,7 @@ exports.disconnect = disconnect;
16
15
  const events_1 = __importDefault(require("./events"));
17
16
  const geo_1 = require("./geo");
18
17
  const lodash_1 = __importDefault(require("lodash"));
18
+ const cross_1 = require("./cross");
19
19
  class Connection {
20
20
  socket;
21
21
  started = new Date();
@@ -41,19 +41,16 @@ class Connection {
41
41
  events_1.default.emit('connectionNewIp', this, this._cachedIp, this.ctx.ip);
42
42
  this._cachedIp = undefined;
43
43
  }
44
- return this.ctx?.ip || (this._cachedIp ??= normalizeIp(this.socket.remoteAddress || ''));
44
+ return this.ctx?.ip || (this._cachedIp ??= (0, cross_1.normalizeIp)(this.socket.remoteAddress || ''));
45
45
  }
46
46
  get secure() {
47
47
  return this.socket.server.cert > '';
48
48
  }
49
49
  }
50
50
  exports.Connection = Connection;
51
- function normalizeIp(ip) {
52
- return ip.replace(/^::ffff:/, ''); // simplify ipv6-mapped addresses
53
- }
54
51
  const all = [];
55
52
  function newConnection(socket) {
56
- const ip = normalizeIp(socket.remoteAddress || '');
53
+ const ip = (0, cross_1.normalizeIp)(socket.remoteAddress || '');
57
54
  const res = events_1.default.emit('newSocket', { socket, ip });
58
55
  const msg = res?.isDefaultPrevented() ? 'plugin (newSocket)' : res?.find(lodash_1.default.isString);
59
56
  if (!msg)
@@ -90,7 +87,7 @@ exports.disconnectionsLog = [];
90
87
  function disconnect(what, logMessage = '') {
91
88
  if ('socket' in what)
92
89
  what = what.socket;
93
- const ip = normalizeIp(what.remoteAddress || '');
90
+ const ip = (0, cross_1.normalizeIp)(what.remoteAddress || '');
94
91
  if (logMessage)
95
92
  console.debug("Disconnection:", logMessage, ip);
96
93
  (0, geo_1.ip2country)(ip).then(res => {
package/src/consoleLog.js CHANGED
@@ -17,6 +17,7 @@ f?.on('error', err => {
17
17
  console.error("Cannot write console file", argv_1.argv.consoleFile, String(err));
18
18
  });
19
19
  let terminalOutputBroken = false;
20
+ let lastTerminalDate = '';
20
21
  for (const stream of [process.stdout, process.stderr])
21
22
  stream.on('error', err => {
22
23
  if (!isBrokenTerminalOutput(err))
@@ -40,6 +41,9 @@ for (const k of ['log', 'warn', 'error', 'debug']) {
40
41
  events_1.default.emit('console', rec);
41
42
  f?.write(`${(0, cross_1.formatTimestamp)(ts)} [${k}] ${msg}\n`);
42
43
  if (!terminalOutputBroken) {
44
+ const date = (0, cross_1.formatDate)(ts);
45
+ if (date !== lastTerminalDate)
46
+ console.log.original(lastTerminalDate = date);
43
47
  try {
44
48
  return original((0, cross_1.formatTime)(ts), ...args);
45
49
  } // bundled nodejs doesn't have locales (and apparently uses en-US)
package/src/const.js CHANGED
@@ -49,7 +49,7 @@ const path_1 = require("path");
49
49
  const cross_1 = require("./cross");
50
50
  const argv_1 = require("./argv");
51
51
  __exportStar(require("./cross-const"), exports);
52
- exports.API_VERSION = 13.5;
52
+ exports.API_VERSION = 13.71;
53
53
  exports.COMPATIBLE_API_VERSION = 1; // the day we break with the past, we'll update this
54
54
  // you can add arguments with this file, currently used for the update process on mac/linux.
55
55
  // we are using homedir because it's the only stable path both the old and new process can agree on (open() doesn't preserve cwd)
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.HTTP_MESSAGES = exports.HTTP_INSUFFICIENT_STORAGE = exports.HTTP_SERVICE_UNAVAILABLE = exports.HTTP_SERVER_ERROR = exports.HTTP_TOO_MANY_REQUESTS = exports.HTTP_FAILED_DEPENDENCY = exports.HTTP_LOCKED = exports.HTTP_FOOL = exports.HTTP_RANGE_NOT_SATISFIABLE = exports.HTTP_PAYLOAD_TOO_LARGE = exports.HTTP_PRECONDITION_FAILED = exports.HTTP_LENGTH_REQUIRED = exports.HTTP_CONFLICT = exports.HTTP_NOT_ACCEPTABLE = exports.HTTP_METHOD_NOT_ALLOWED = exports.HTTP_NOT_FOUND = exports.HTTP_FORBIDDEN = exports.HTTP_UNAUTHORIZED = exports.HTTP_BAD_REQUEST = exports.HTTP_NOT_MODIFIED = exports.HTTP_TEMPORARY_REDIRECT = exports.HTTP_MOVED_PERMANENTLY = exports.HTTP_PARTIAL_CONTENT = exports.HTTP_NO_CONTENT = exports.HTTP_CREATED = exports.HTTP_OK = exports.CFG = exports.MB = exports.KB = exports.BASIC_AUTHENTICATE_HEADER = exports.EMBEDDED_LANGUAGE = exports.MASK_IN_TESTS = exports.HIDE_IN_TESTS = exports.ALLOW_SESSION_IP_CHANGE = exports.PREVIOUS_TAG = exports.MTIME_CHECK = exports.UPLOAD_TEMP_HASH = exports.UPLOAD_TEMP_PREFIX = exports.HFS_REPO = exports.PLUGIN_CUSTOM_REST_PREFIX = exports.NBSP = exports.PORT_DISABLED = exports.ICONS_URI = exports.PLUGINS_PUB_URI = exports.API_URI = exports.ADMIN_URI = exports.FRONTEND_URI = exports.SPECIAL_URI = void 0;
3
+ exports.HTTP_MESSAGES = exports.HTTP_INSUFFICIENT_STORAGE = exports.HTTP_SERVICE_UNAVAILABLE = exports.HTTP_SERVER_ERROR = exports.HTTP_TOO_MANY_REQUESTS = exports.HTTP_FAILED_DEPENDENCY = exports.HTTP_LOCKED = exports.HTTP_FOOL = exports.HTTP_RANGE_NOT_SATISFIABLE = exports.HTTP_PAYLOAD_TOO_LARGE = exports.HTTP_PRECONDITION_FAILED = exports.HTTP_LENGTH_REQUIRED = exports.HTTP_CONFLICT = exports.HTTP_NOT_ACCEPTABLE = exports.HTTP_METHOD_NOT_ALLOWED = exports.HTTP_NOT_FOUND = exports.HTTP_FORBIDDEN = exports.HTTP_UNAUTHORIZED = exports.HTTP_BAD_REQUEST = exports.HTTP_TEMPORARY_REDIRECT_KEEP_METHOD = exports.HTTP_NOT_MODIFIED = exports.HTTP_TEMPORARY_REDIRECT = exports.HTTP_MOVED_PERMANENTLY = exports.HTTP_PARTIAL_CONTENT = exports.HTTP_NO_CONTENT = exports.HTTP_CREATED = exports.HTTP_OK = exports.CFG = exports.MB = exports.KB = exports.BASIC_AUTHENTICATE_HEADER = exports.EMBEDDED_LANGUAGE = exports.MASK_IN_TESTS = exports.HIDE_IN_TESTS = exports.ALLOW_SESSION_IP_CHANGE = exports.PREVIOUS_TAG = exports.MTIME_CHECK = exports.UPLOAD_TEMP_HASH = exports.UPLOAD_TEMP_PREFIX = exports.PLUGIN_USAGE_URL = exports.HFS_REPO = exports.PLUGIN_CUSTOM_REST_PREFIX = exports.NBSP = exports.PORT_DISABLED = exports.ICONS_URI = exports.PLUGINS_PUB_URI = exports.API_URI = exports.ADMIN_URI = exports.FRONTEND_URI = exports.SPECIAL_URI = void 0;
4
4
  exports.SPECIAL_URI = '/~/';
5
5
  exports.FRONTEND_URI = exports.SPECIAL_URI + 'frontend/';
6
6
  exports.ADMIN_URI = exports.SPECIAL_URI + 'admin/';
@@ -11,6 +11,7 @@ exports.PORT_DISABLED = -1;
11
11
  exports.NBSP = '\xA0';
12
12
  exports.PLUGIN_CUSTOM_REST_PREFIX = '_';
13
13
  exports.HFS_REPO = 'rejetto/hfs';
14
+ exports.PLUGIN_USAGE_URL = 'https://hfs-api.rejetto.com/plugin-usage';
14
15
  exports.UPLOAD_TEMP_PREFIX = 'hfs$upload-';
15
16
  exports.UPLOAD_TEMP_HASH = 'upload-temp-hash';
16
17
  exports.MTIME_CHECK = 'x-mtime-check';
@@ -18,12 +19,12 @@ exports.PREVIOUS_TAG = 'previous';
18
19
  exports.ALLOW_SESSION_IP_CHANGE = 'allow_session_ip_change';
19
20
  exports.HIDE_IN_TESTS = 'hideInTests'; // elements that have variable size, where masking would produce changes, must be hidden
20
21
  exports.MASK_IN_TESTS = 'maskInTests';
21
- exports.EMBEDDED_LANGUAGE = 'en'; // frontend includes this language in the code, and not need to import the translation-json
22
+ exports.EMBEDDED_LANGUAGE = 'en';
22
23
  exports.BASIC_AUTHENTICATE_HEADER = 'Basic realm="HFS"';
23
24
  exports.KB = 1024;
24
25
  exports.MB = exports.KB * exports.KB;
25
26
  exports.CFG = constMap([
26
- 'accounts', 'acme_domain', 'acme_renew', 'admin_net', 'allowed_referer',
27
+ 'accounts', 'acme_domain', 'acme_renew', 'acme_challenge', 'acme_dns', 'admin_lang', 'admin_net', 'allowed_referer',
27
28
  'authorization_header', 'auto_basic', 'auto_check_update', 'auto_play_seconds',
28
29
  'base_url', 'block', 'cache_control_disk_files', 'cert', 'comments_storage',
29
30
  'create-admin', 'debug', 'delete_unfinished_uploads_after',
@@ -33,16 +34,16 @@ exports.CFG = constMap([
33
34
  'force_https', 'force_lang', 'force_webdav_login',
34
35
  'geo_allow', 'geo_allow_unknown', 'geo_enable', 'geo_ignore_net', 'geo_list',
35
36
  'ignore_proxies', 'invert_order', 'keep_session_alive', 'listen_interface',
36
- 'localhost_admin', 'log', 'log_api', 'log_gui', 'log_host', 'log_rotation', 'log_spam',
37
+ 'localhost_admin', 'log', 'log_api', 'log_gui', 'log_host', 'log_rotation', 'log_spam', 'log_spam_regex',
37
38
  'log_ua', 'mapped_port', 'max_downloads', 'max_downloads_per_account',
38
39
  'max_downloads_per_ip', 'max_kbps', 'max_kbps_per_ip', 'menu_at_top', 'mime',
39
40
  'min_available_mb', 'open_browser_at_start', 'outbound_interface', 'outbound_proxy', 'own_upload_delete_hours',
40
41
  'page_size', 'plugins_config', 'port', 'private_key', 'proxies', 'roots', 'server_code',
41
- 'session_duration', 'show_hidden_files', 'show_uploader', 'size_1024',
42
+ 'session_duration', 'share_usage_stats', 'show_hidden_files', 'show_uploader', 'size_1024',
42
43
  'smart_unc_folder_detection', 'sort_by', 'sort_numerics', 'split_uploads',
43
44
  'suspend_plugins', 'theme', 'tile_size', 'title', 'title_with_path', 'track_ips',
44
45
  'update_to_beta', 'upnp_enabled', 'version', 'vfs', 'webdav_initial_auth',
45
- 'zip_calculate_size_for_seconds', 'https_port'
46
+ 'zip_calculate_size_for_seconds', 'https_port', 'require_upload_approval_for_active_content',
46
47
  ]);
47
48
  function constMap(a) {
48
49
  return Object.fromEntries(a.map(x => [x, x]));
@@ -54,6 +55,7 @@ exports.HTTP_PARTIAL_CONTENT = 206;
54
55
  exports.HTTP_MOVED_PERMANENTLY = 301;
55
56
  exports.HTTP_TEMPORARY_REDIRECT = 302;
56
57
  exports.HTTP_NOT_MODIFIED = 304;
58
+ exports.HTTP_TEMPORARY_REDIRECT_KEEP_METHOD = 307;
57
59
  exports.HTTP_BAD_REQUEST = 400;
58
60
  exports.HTTP_UNAUTHORIZED = 401;
59
61
  exports.HTTP_FORBIDDEN = 403;