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/cross.js CHANGED
@@ -77,6 +77,7 @@ exports.xlate = xlate;
77
77
  exports.normalizeHost = normalizeHost;
78
78
  exports.isIpLocalHost = isIpLocalHost;
79
79
  exports.isIpLan = isIpLan;
80
+ exports.normalizeIp = normalizeIp;
80
81
  exports.ipForUrl = ipForUrl;
81
82
  exports.escapeHTML = escapeHTML;
82
83
  exports.promiseBestEffort = promiseBestEffort;
@@ -142,7 +143,7 @@ exports.defaultPerms = {
142
143
  };
143
144
  exports.PERM_KEYS = typedKeys(exports.defaultPerms);
144
145
  exports.VFS_STORED_KEYS = ['name', 'source', 'masks', 'default', 'accept', 'rename',
145
- 'mime', 'url', 'target', 'comment', 'icon', 'order', 'see_without_probing', 'children', ...exports.PERM_KEYS];
146
+ 'mime', 'url', 'target', 'comment', 'icon', 'order', 'gui_asset', 'see_without_probing', 'children', ...exports.PERM_KEYS];
146
147
  function isWhoObject(v) {
147
148
  return v !== null && typeof v === 'object' && !Array.isArray(v);
148
149
  }
@@ -460,7 +461,20 @@ function isIpLocalHost(ip) {
460
461
  return ip === '::1' || /^(?:::ffff:)?127\.0\.0\.1$/i.test(ip);
461
462
  }
462
463
  function isIpLan(ip) {
463
- return /^(?:10\.|172\.(1[6-9]|2\d|3[01])\.|192\.168\.|fe80::)/.test(ip);
464
+ return /^(?:10\.|172\.(1[6-9]|2\d|3[01])\.|192\.168\.|169\.254\.|f[cd][\da-f]{2}:|fe[89ab][\da-f]:)/i.test(normalizeIp(ip));
465
+ }
466
+ function normalizeIp(ip) {
467
+ // proxy headers may contain invalid addresses; do not interpret URL paths or credentials as an IP
468
+ if (!/^[\da-f:.\[\]]+$/i.test(ip))
469
+ return ip;
470
+ // URL canonicalizes IPv6 and alternate IPv4 spellings before matching
471
+ const host = try_(() => new URL(`http://${ip.includes(':') && !ip.startsWith('[') ? `[${ip}]` : ip}`).hostname) || ip;
472
+ const bare = host.startsWith('[') ? host.slice(1, -1) : host;
473
+ if (bare.startsWith('::ffff:')) {
474
+ const words = bare.slice(7).split(':').map(x => parseInt(x, 16));
475
+ return words.flatMap(x => [x >> 8, x & 255]).join('.');
476
+ }
477
+ return bare;
464
478
  }
465
479
  function ipForUrl(ip) {
466
480
  if (ip.startsWith('['))
package/src/customHtml.js CHANGED
@@ -14,8 +14,9 @@ const config_1 = require("./config");
14
14
  const plugins_1 = require("./plugins");
15
15
  const lodash_1 = __importDefault(require("lodash"));
16
16
  const FILE = 'custom.html';
17
- exports.customHtmlSections = ['style', 'script', 'beforeHeader', 'afterHeader', 'afterMenuBar', 'afterList',
18
- 'footer', 'top', 'bottom', 'afterEntryName', 'beforeLogin', 'unauthorized', 'htmlHead', 'userPanelAfterInfo'];
17
+ exports.customHtmlSections = ['style', 'script', 'beforeHeader', 'afterHeader', 'afterMenuBar',
18
+ 'footer', 'top', 'bottom', 'afterEntryName', 'beforeLogin', 'unauthorized', 'htmlHead', 'userPanelAfterInfo',
19
+ 'afterList', 'beforeOptions', 'afterOptions'];
19
20
  exports.customHtml = watchLoadCustomHtml();
20
21
  exports.disableCustomHtml = (0, config_1.defineConfig)(misc_1.CFG.disable_custom_html, false);
21
22
  function watchLoadCustomHtml(folder = '') {
@@ -19,6 +19,7 @@ const fs_1 = __importDefault(require("fs"));
19
19
  const promises_1 = require("fs/promises");
20
20
  const path_1 = require("path");
21
21
  const upload_1 = require("./upload");
22
+ const serveFile_1 = require("./serveFile");
22
23
  const misc_1 = require("./misc");
23
24
  const uploadOwners_1 = require("./uploadOwners");
24
25
  const config_1 = require("./config");
@@ -59,10 +60,21 @@ exports.frontEndApis = {
59
60
  return;
60
61
  if (!isAdmin)
61
62
  upload = lodash_1.default.omit(upload, 'ip');
62
- return { upload };
63
+ return { upload, activeContent: isAdmin && (0, serveFile_1.isActiveContentMime)((0, serveFile_1.getNodeMime)(node)) };
63
64
  }))
64
65
  };
65
66
  },
67
+ async set_upload_approved({ uri, approved }, ctx) {
68
+ (0, misc_1.apiAssertTypes)({ string: { uri }, boolean: { approved } });
69
+ if (!(0, adminApis_1.ctxAdminAccess)(ctx))
70
+ return new apiMiddleware_1.ApiError(const_1.HTTP_UNAUTHORIZED);
71
+ const node = await (0, vfs_1.urlToNode)(uri, ctx);
72
+ if (!node?.source)
73
+ return new apiMiddleware_1.ApiError(const_1.HTTP_NOT_FOUND);
74
+ if (!(0, serveFile_1.isActiveContentMime)((0, serveFile_1.getNodeMime)(node)))
75
+ return new apiMiddleware_1.ApiError(const_1.HTTP_BAD_REQUEST);
76
+ return await (0, upload_1.setUploadApproved)(node.source, approved) ? {} : new apiMiddleware_1.ApiError(const_1.HTTP_FAILED_DEPENDENCY);
77
+ },
66
78
  async create_folder({ uri, name }, ctx) {
67
79
  (0, misc_1.apiAssertTypes)({ string: { uri, name } });
68
80
  try {
@@ -115,10 +127,24 @@ exports.frontEndApis = {
115
127
  return moveFiles(uri_from, uri_to, ctx);
116
128
  },
117
129
  async copy_files({ uri_from, uri_to }, ctx) {
118
- return moveFiles(uri_from, uri_to, ctx, (srcNode, dest) => // override behavior
119
- (0, vfs_1.statusCodeForMissingPerm)(srcNode, 'can_read', ctx)
120
- || (0, promises_1.copyFile)(srcNode.source, dest, fs_1.default.constants.COPYFILE_EXCL | fs_1.default.constants.COPYFILE_FICLONE) // .source is checked by moveFiles
121
- .catch(e => e.code || String(e)));
130
+ return moveFiles(uri_from, uri_to, ctx, async (srcNode, dest) => {
131
+ const err = (0, vfs_1.statusCodeForMissingPerm)(srcNode, 'can_read', ctx);
132
+ if (err)
133
+ return err;
134
+ const upload = await (0, upload_1.getUploadMeta)(srcNode.source);
135
+ try {
136
+ return await (0, upload_1.whileUploadMetaPending)(dest, async () => {
137
+ await (0, promises_1.copyFile)(srcNode.source, dest, fs_1.default.constants.COPYFILE_EXCL | fs_1.default.constants.COPYFILE_FICLONE);
138
+ if (upload && !await (0, upload_1.saveUploadMeta)(dest, upload)) {
139
+ await (0, promises_1.unlink)(dest);
140
+ return const_1.HTTP_SERVER_ERROR;
141
+ }
142
+ });
143
+ }
144
+ catch (e) {
145
+ return e.code || String(e);
146
+ }
147
+ });
122
148
  },
123
149
  async comment({ uri, comment }, ctx) {
124
150
  (0, misc_1.apiAssertTypes)({ string: { uri, comment } });
@@ -197,6 +223,8 @@ async function moveFiles(uri_from, uri_to, ctx, override) {
197
223
  const src = srcNode?.source;
198
224
  if (!src)
199
225
  return const_1.HTTP_NOT_FOUND;
226
+ if ((0, upload_1.isUploading)(src))
227
+ return const_1.HTTP_CONFLICT;
200
228
  if (!override && (0, webdav_1.isWebdavLocked)(srcNode.vfsPath, ctx))
201
229
  return ctx.status;
202
230
  const destName = (0, path_1.basename)(src);
@@ -212,12 +240,19 @@ async function moveFiles(uri_from, uri_to, ctx, override) {
212
240
  return ctx.status;
213
241
  if (lodash_1.default.isFunction(override))
214
242
  return override?.(srcNode, dest);
243
+ const upload = await (0, upload_1.getUploadMeta)(src);
215
244
  return (0, vfs_1.statusCodeForMissingPerm)(srcNode, 'can_delete', ctx)
216
245
  || (0, promises_1.rename)(src, dest).catch(async (e) => {
217
246
  if (e.code !== 'EXDEV')
218
247
  throw e; // exdev = different drive
219
- await (0, promises_1.copyFile)(src, dest);
220
- await (0, promises_1.unlink)(src);
248
+ await (0, upload_1.whileUploadMetaPending)(dest, async () => {
249
+ await (0, promises_1.copyFile)(src, dest);
250
+ if (upload && !await (0, upload_1.saveUploadMeta)(dest, upload)) {
251
+ await (0, promises_1.unlink)(dest);
252
+ throw Error("couldn't move upload metadata");
253
+ }
254
+ await (0, promises_1.unlink)(src);
255
+ });
221
256
  }).then(() => (0, misc_1.moveStoredFileAttrs)(src, dest))
222
257
  .then(() => (0, uploadOwners_1.moveUploadOwner)(srcNode.vfsPath, destUri, dest))
223
258
  .catch(e => e.code || String(e));
@@ -227,6 +262,8 @@ async function moveFiles(uri_from, uri_to, ctx, override) {
227
262
  async function requestedRename(node, newName, ctx) {
228
263
  if (!node)
229
264
  throw new apiMiddleware_1.ApiError(const_1.HTTP_NOT_FOUND);
265
+ if (node.source && (0, upload_1.isUploading)(node.source))
266
+ throw new apiMiddleware_1.ApiError(const_1.HTTP_CONFLICT);
230
267
  // requestedRename is exported, so keep disk rename confinement here even when callers pre-validate
231
268
  if (!(0, util_files_1.isValidFileName)(newName))
232
269
  throw new apiMiddleware_1.ApiError(const_1.HTTP_BAD_REQUEST);
package/src/github.js CHANGED
@@ -12,7 +12,6 @@ exports.readOnlinePlugin = readOnlinePlugin;
12
12
  exports.readOnlineCompatiblePlugin = readOnlineCompatiblePlugin;
13
13
  exports.getFolder2repo = getFolder2repo;
14
14
  exports.apiGithubPaginated = apiGithubPaginated;
15
- exports.searchPlugins = searchPlugins;
16
15
  const events_1 = __importDefault(require("./events"));
17
16
  const misc_1 = require("./misc");
18
17
  const plugins_1 = require("./plugins");
@@ -213,7 +212,7 @@ async function apiGithub(uri) {
213
212
  : e;
214
213
  });
215
214
  }
216
- async function* apiGithubPaginated(uri) {
215
+ async function* apiGithubPaginated(uri, { requireComplete = false } = {}) {
217
216
  uri += uri.includes('?') ? '&' : '?';
218
217
  const PAGE_SIZE = 100;
219
218
  let page = 1;
@@ -221,6 +220,8 @@ async function* apiGithubPaginated(uri) {
221
220
  try {
222
221
  while (1) {
223
222
  const res = await apiGithub(uri + `page=${page++}&per_page=${PAGE_SIZE}`);
223
+ if (requireComplete && res.incomplete_results)
224
+ throw Error("Incomplete GitHub search results");
224
225
  const a = res.items || res; // "search/repositories" returns an object, while "releases" returns simply an array
225
226
  for (const x of a)
226
227
  yield x;
@@ -231,47 +232,13 @@ async function* apiGithubPaginated(uri) {
231
232
  }
232
233
  }
233
234
  catch (e) {
234
- if (e.message !== '422') // for some strange reason github api is returning this error if we search repos for a missing user, instead of empty set
235
+ if (requireComplete || e.message !== '422') // for some strange reason github api is returning this error if we search repos for a missing user, instead of empty set
235
236
  throw e;
236
237
  }
237
238
  }
238
239
  async function isPluginBlacklisted(repo) {
239
240
  return (0, exports.getProjectInfo)().then(x => x?.repo_blacklist?.[repo]?.message || '', () => undefined);
240
241
  }
241
- async function searchPlugins(text = '', { skipRepos = [''] } = {}) {
242
- const seen = new Set();
243
- return new misc_1.AsapStream(pluginPromises());
244
- async function* pluginPromises() {
245
- // github doesn't allow complex search, so we have to do it multiple times and merge the results
246
- const searches = [
247
- ...text.split(' ').filter(Boolean).slice(0, 2).map(x => 'user:' + encodeURI(x)), // first 2 words can be the author of the plugin
248
- encodeURI(text), // search elsewhere, and results after the author search
249
- ];
250
- for (const term of searches) {
251
- for await (const it of apiGithubPaginated(`search/repositories?q=topic:hfs-plugin+${term}`)) {
252
- const repo = it.full_name;
253
- if (!repo || seen.has(repo)) // avoid duplicates, as we search multiple times
254
- continue;
255
- seen.add(repo);
256
- if (skipRepos.includes(repo))
257
- continue;
258
- yield (async () => {
259
- if (await isPluginBlacklisted(repo))
260
- return;
261
- const pl = await readOnlineCompatiblePlugin(repo, it.default_branch).catch(() => undefined);
262
- if (!pl)
263
- return;
264
- Object.assign(pl, {
265
- repo,
266
- downloading: exports.downloading[repo],
267
- license: it.license?.spdx_id,
268
- }, lodash_1.default.pick(it, ['pushed_at', 'stargazers_count', 'default_branch']));
269
- return pl;
270
- })();
271
- }
272
- }
273
- }
274
- }
275
242
  persistence_1.storedMap.ready().then(() => persistence_1.storedMap.del('alerts')); // remove legacy
276
243
  const cachedCentralInfo = persistence_1.storedMap.singleSync('cachedCentralInfo', ''); // persisting it could also be useful for no-internet instances, so that you can provide a fresher copy
277
244
  exports.blacklistedInstalledPlugins = [];
@@ -292,6 +259,8 @@ exports.getProjectInfo = (0, misc_1.debounceAsync)(async () => {
292
259
  }
293
260
  else
294
261
  obj = { ...cachedCentralInfo.get() || JSON.parse(builtInJson) }; // fall back to built-in
262
+ // older catalogs predate DNS providers; an explicit empty list still disables the built-ins
263
+ obj.acmeDns ??= JSON.parse(builtInJson).acmeDns;
295
264
  // merge byVersions info in the main object but collect alerts separately to preserve multiple instances
296
265
  const newAlerts = [obj.alert];
297
266
  for (const [ver, more] of Object.entries((0, misc_1.popKey)(obj, 'byVersion') || {}))
package/src/i18n.js CHANGED
@@ -37,12 +37,21 @@ function i18nFromTranslations(translations, embedded = 'en') {
37
37
  let selectedLang = ''; // keep track of where we find the translation
38
38
  const { embedded } = state;
39
39
  const langs = Object.keys(state.translations);
40
- for (const key of keys) {
40
+ const english = state.translations[embedded]?.translate || {};
41
+ for (let key of keys) {
42
+ const originalKey = key;
43
+ if (!(key in english))
44
+ key = Object.keys(english).find(k => english[k] === key) || key; // support extensions still passing English text
45
+ const legacyKey = english[key] === key ? undefined : english[key];
41
46
  for (const lang of searchLangs)
42
- if (found = state.translations[selectedLang = lang]?.translate?.[key])
47
+ if (found = state.translations[selectedLang = lang]?.translate?.[key]
48
+ || legacyKey && state.translations[selectedLang = lang]?.translate?.[legacyKey])
43
49
  break;
44
50
  if (found)
45
51
  break;
52
+ if (!fallback && english[key])
53
+ fallback = english[key];
54
+ key = originalKey;
46
55
  if (!warns.has(key) && langs.length && langs[0] !== embedded) {
47
56
  warns.add(key);
48
57
  console.debug("i18n miss:", key);
@@ -85,7 +94,7 @@ function i18nFromTranslations(translations, embedded = 'en') {
85
94
  }
86
95
  return pick(other);
87
96
  function pick(s) {
88
- return s.replace('#', String(v));
97
+ return s.replaceAll('#', String(v)).replace(/\{(\w+)\}/g, (_, key) => params[key] ?? '');
89
98
  }
90
99
  }
91
100
  }
@@ -118,7 +127,10 @@ function i18nFromTranslations(translations, embedded = 'en') {
118
127
  t,
119
128
  state,
120
129
  getLangs,
121
- i18nWrapperProps: () => ({ lang: state.disabled ? state.embedded : getLangs()[0] || state.embedded }),
130
+ i18nWrapperProps: () => {
131
+ const lang = state.disabled ? state.embedded : getLangs()[0] || state.embedded;
132
+ return { lang, dir: lang === 'ar' ? 'rtl' : 'ltr' };
133
+ },
122
134
  useI18N() {
123
135
  (0, valtio_1.useSnapshot)(state);
124
136
  return { t };
package/src/index.js CHANGED
@@ -34,6 +34,7 @@ const roots_1 = require("./roots");
34
34
  const events_1 = __importDefault(require("./events"));
35
35
  const ips_1 = require("./ips");
36
36
  require("./outboundProxy");
37
+ require("./pluginUsage");
37
38
  (0, assert_1.ok)(lodash_1.default.intersection(Object.keys(frontEndApis_1.frontEndApis), Object.keys(adminApis_1.adminApis)).length === 0); // they share same endpoints, don't clash
38
39
  if (new config_1.Version(process.versions.node).olderThan('18.15.0')) {
39
40
  console.error("Node.js 18.15+ is required, please update");
@@ -53,11 +54,11 @@ exports.app.use(middlewares_1.sessionMiddleware)
53
54
  .use(middlewares_1.prepareState)
54
55
  .use(ips_1.trackIpsMw)
55
56
  .use(middlewares_1.gzipper)
56
- .use(middlewares_1.paramsDecoder) // must be done before plugins, so they can manipulate params
57
- .use(middlewares_1.headRequests)
58
- .use(roots_1.rootsMiddleware)
57
+ .use(roots_1.rootsHostGuard)
59
58
  .use(log_1.logMw)
60
59
  .use(middlewares_1.someSecurity)
60
+ .use(middlewares_1.paramsDecoder) // must be done before plugins, so they can manipulate params
61
+ .use(roots_1.rootsMiddleware)
61
62
  .use(throttler_1.throttler)
62
63
  .use(plugins_1.pluginsMiddleware)
63
64
  .use((0, koa_mount_1.default)(const_1.API_URI, (0, apiMiddleware_1.apiMiddleware)({ ...frontEndApis_1.frontEndApis, ...adminApis_1.adminApis })))
package/src/lang.js CHANGED
@@ -4,6 +4,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.normalizeLangCode = normalizeLangCode;
7
+ exports.langCodeForLookup = langCodeForLookup;
7
8
  exports.code2file = code2file;
8
9
  exports.file2code = file2code;
9
10
  exports.getLangData = getLangData;
@@ -17,17 +18,19 @@ const const_1 = require("./const");
17
18
  const plugins_1 = require("./plugins");
18
19
  const path_1 = require("path");
19
20
  const lodash_1 = __importDefault(require("lodash"));
20
- const PREFIX = 'hfs-lang-';
21
21
  const SUFFIX = '.json';
22
22
  function normalizeLangCode(code) {
23
23
  code = code.toLowerCase().split(';', 1)[0].trim();
24
24
  return /^[a-z]{2,3}(-[a-z0-9]{2,8})*$/.test(code) ? code : '';
25
25
  }
26
- function code2file(code) {
27
- return PREFIX + code.toLowerCase() + SUFFIX;
26
+ function langCodeForLookup(code) {
27
+ return ['zh-tw', 'zh-hk', 'zh-mo'].includes(code) ? 'zh-hant' : code;
28
28
  }
29
- function file2code(fn) {
30
- return fn.replace(PREFIX, '').replace(SUFFIX, '');
29
+ function code2file(code, admin = false) {
30
+ return (admin ? 'hfs-admin-lang-' : 'hfs-lang-') + code.toLowerCase() + SUFFIX;
31
+ }
32
+ function file2code(fn, admin = false) {
33
+ return fn.replace(admin ? 'hfs-admin-lang-' : 'hfs-lang-', '').replace(SUFFIX, '');
31
34
  }
32
35
  const cache = (0, expiringCache_1.expiringCache)(3_000); // 3 seconds for both a good dx and acceptable performance
33
36
  async function getLangData(ctxOrLangCsv) {
@@ -40,12 +43,14 @@ async function getLangData(ctxOrLangCsv) {
40
43
  }
41
44
  const csv = ctxOrLangCsv.toLowerCase();
42
45
  return cache.try(csv, async () => {
43
- const langs = csv.split(',').map(normalizeLangCode).filter(Boolean);
46
+ const langs = csv.split(',').map(normalizeLangCode).map(langCodeForLookup).filter(Boolean);
47
+ if (!langs.includes(const_1.EMBEDDED_LANGUAGE))
48
+ langs.push(const_1.EMBEDDED_LANGUAGE);
44
49
  const ret = {};
45
50
  let i = 0;
46
51
  while (i < langs.length) {
47
52
  let k = langs[i] || ''; // shut up ts
48
- if (!k || k === const_1.EMBEDDED_LANGUAGE)
53
+ if (!k)
49
54
  break;
50
55
  const fn = code2file(k);
51
56
  try {
@@ -75,15 +80,22 @@ async function getLangData(ctxOrLangCsv) {
75
80
  let forceLangData;
76
81
  let undo;
77
82
  (0, config_1.defineConfig)(misc_1.CFG.force_lang, '', v => {
83
+ v = langCodeForLookup(v);
78
84
  undo?.();
79
85
  if (!v)
80
86
  return forceLangData = undefined;
81
87
  const translation = embedded_1.default[v];
82
- forceLangData = { [v]: translation };
88
+ forceLangData = {
89
+ [v]: translation,
90
+ ...v === const_1.EMBEDDED_LANGUAGE ? {} : { [const_1.EMBEDDED_LANGUAGE]: embedded_1.default.en },
91
+ };
83
92
  if (v === const_1.EMBEDDED_LANGUAGE)
84
93
  return;
85
94
  const res = (0, watchLoad_1.watchLoad)(code2file(v), data => {
86
- forceLangData = { [v]: (0, misc_1.tryJson)(data) || translation };
95
+ forceLangData = {
96
+ [v]: (0, misc_1.tryJson)(data) || translation,
97
+ [const_1.EMBEDDED_LANGUAGE]: embedded_1.default.en,
98
+ };
87
99
  });
88
100
  undo = res.unwatch;
89
101
  });
@@ -3,6 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
+ const hfs_lang_en_json_1 = __importDefault(require("./hfs-lang-en.json"));
6
7
  const hfs_lang_it_json_1 = __importDefault(require("./hfs-lang-it.json"));
7
8
  const hfs_lang_zh_json_1 = __importDefault(require("./hfs-lang-zh.json"));
8
9
  const hfs_lang_ru_json_1 = __importDefault(require("./hfs-lang-ru.json"));
@@ -11,7 +12,7 @@ const hfs_lang_sr_json_1 = __importDefault(require("./hfs-lang-sr.json"));
11
12
  const hfs_lang_sr_latn_json_1 = __importDefault(require("./hfs-lang-sr-latn.json"));
12
13
  const hfs_lang_ko_json_1 = __importDefault(require("./hfs-lang-ko.json"));
13
14
  const hfs_lang_ms_json_1 = __importDefault(require("./hfs-lang-ms.json"));
14
- const hfs_lang_zh_tw_json_1 = __importDefault(require("./hfs-lang-zh-tw.json"));
15
+ const hfs_lang_zh_hant_json_1 = __importDefault(require("./hfs-lang-zh-hant.json"));
15
16
  const hfs_lang_fr_json_1 = __importDefault(require("./hfs-lang-fr.json"));
16
17
  const hfs_lang_pl_json_1 = __importDefault(require("./hfs-lang-pl.json"));
17
18
  const hfs_lang_pt_br_json_1 = __importDefault(require("./hfs-lang-pt-br.json"));
@@ -29,4 +30,4 @@ const hfs_lang_uk_json_1 = __importDefault(require("./hfs-lang-uk.json"));
29
30
  const hfs_lang_ar_json_1 = __importDefault(require("./hfs-lang-ar.json"));
30
31
  const hfs_lang_lt_json_1 = __importDefault(require("./hfs-lang-lt.json"));
31
32
  const hfs_lang_bg_json_1 = __importDefault(require("./hfs-lang-bg.json"));
32
- exports.default = { it: hfs_lang_it_json_1.default, zh: hfs_lang_zh_json_1.default, ru: hfs_lang_ru_json_1.default, sr: hfs_lang_sr_json_1.default, 'sr-latn': hfs_lang_sr_latn_json_1.default, ko: hfs_lang_ko_json_1.default, ms: hfs_lang_ms_json_1.default, 'zh-tw': hfs_lang_zh_tw_json_1.default, fr: hfs_lang_fr_json_1.default, pl: hfs_lang_pl_json_1.default, 'pt-br': hfs_lang_pt_br_json_1.default, vi: hfs_lang_vi_json_1.default, es: hfs_lang_es_json_1.default, nl: hfs_lang_nl_json_1.default, el: hfs_lang_el_json_1.default, de: hfs_lang_de_json_1.default, fi: hfs_lang_fi_json_1.default, hu: hfs_lang_hu_json_1.default, ja: hfs_lang_ja_json_1.default, ro: hfs_lang_ro_json_1.default, tr: hfs_lang_tr_json_1.default, th: hfs_lang_th_json_1.default, uk: hfs_lang_uk_json_1.default, ar: hfs_lang_ar_json_1.default, lt: hfs_lang_lt_json_1.default, bg: hfs_lang_bg_json_1.default };
33
+ exports.default = { en: hfs_lang_en_json_1.default, it: hfs_lang_it_json_1.default, zh: hfs_lang_zh_json_1.default, ru: hfs_lang_ru_json_1.default, sr: hfs_lang_sr_json_1.default, 'sr-latn': hfs_lang_sr_latn_json_1.default, ko: hfs_lang_ko_json_1.default, ms: hfs_lang_ms_json_1.default, 'zh-hant': hfs_lang_zh_hant_json_1.default, fr: hfs_lang_fr_json_1.default, pl: hfs_lang_pl_json_1.default, 'pt-br': hfs_lang_pt_br_json_1.default, vi: hfs_lang_vi_json_1.default, es: hfs_lang_es_json_1.default, nl: hfs_lang_nl_json_1.default, el: hfs_lang_el_json_1.default, de: hfs_lang_de_json_1.default, fi: hfs_lang_fi_json_1.default, hu: hfs_lang_hu_json_1.default, ja: hfs_lang_ja_json_1.default, ro: hfs_lang_ro_json_1.default, tr: hfs_lang_tr_json_1.default, th: hfs_lang_th_json_1.default, uk: hfs_lang_uk_json_1.default, ar: hfs_lang_ar_json_1.default, lt: hfs_lang_lt_json_1.default, bg: hfs_lang_bg_json_1.default };
@@ -1,7 +1,6 @@
1
1
  {
2
2
  "author": "Hussein M. Alghazal (hmg3099)",
3
3
  "version": 1.0,
4
- "hfs_version": "0.57.5",
5
4
  "translate": {
6
5
  "Select": "اختر",
7
6
  "n_files": "{n,plural,one{# file} other{# files}}",
@@ -39,6 +38,7 @@
39
38
  "Password": "كلمة المرور",
40
39
  "login_untrusted": "تم إيقاف تسجيل الدخول: لا يمكن الوثوق بهوية الخادم",
41
40
  "login_bad_credentials": "بيانات اعتماد غير صالحة",
41
+ "confirm_url_login": "يحاول هذا الرابط تسجيل دخولك باسم «{username}»",
42
42
  "login_bad_cookies": "الكوكيز لا تعمل - فشل تسجيل الدخول",
43
43
  "User panel": "لوحة المستخدم",
44
44
  "Change password": "تغيير كلمة المرور",
@@ -110,6 +110,8 @@
110
110
  "Timestamp": "الطابع الزمني",
111
111
  "Show": "عرض",
112
112
  "Loading failed": "فشل التحميل",
113
+ "Download limit reached": "تم بلوغ حد التنزيلات",
114
+ "Retry": "إعادة المحاولة",
113
115
  "Rename": "إعادة تسمية",
114
116
  "Tiles mode:": "وضع الشبكة:",
115
117
  "off": "إيقاف",
@@ -178,6 +180,9 @@
178
180
  "No": "لا",
179
181
  "esc_again": "انقر على ESC ثانية لاغلاق",
180
182
  "insufficient storage": "مساحة تخزين غير كافية",
181
- "Account disabled": "تم تعطيل الحساب"
183
+ "Account disabled": "تم تعطيل الحساب",
184
+ "upload_name": "تحميل بالاسم الجديد",
185
+ "Uploading": "جارٍ التحميل",
186
+ "proxy_413": "تحقق من هذا الحد على الخادم الوكيل"
182
187
  }
183
188
  }
@@ -1,7 +1,6 @@
1
1
  {
2
2
  "author": "Ангел Трънгалов",
3
3
  "version": 1.0,
4
- "hfs_version": "0.57.5",
5
4
  "translate": {
6
5
  "Select": "Избери",
7
6
  "n_files": "{n,plural,one{# файл} other{# файлове}}",
@@ -39,6 +38,7 @@
39
38
  "Password": "Парола",
40
39
  "login_untrusted": "Вход отказан: самоличността на сървъра не е надеждна",
41
40
  "login_bad_credentials": "Невалидни данни за вход",
41
+ "confirm_url_login": "Тази връзка се опитва да ви впише като „{username}“",
42
42
  "login_bad_cookies": "Бисквитките не работят - вход неуспешен",
43
43
  "User panel": "Панел на потребителя",
44
44
  "Change password": "Смяна на парола",
@@ -108,6 +108,8 @@
108
108
  "Timestamp": "Печат на време",
109
109
  "Show": "Покажи",
110
110
  "Loading failed": "Зареждането неуспешно",
111
+ "Download limit reached": "Достигнат е лимитът за изтегляния",
112
+ "Retry": "Опитай отново",
111
113
  "Rename": "Преименувай",
112
114
  "Tiles mode:": "Режим плочки:",
113
115
  "off": "изкл.",
@@ -176,6 +178,9 @@
176
178
  "No": "Не",
177
179
  "esc_again": "Натиснете ESC два пъти за затваряне",
178
180
  "insufficient storage": "недостатъчно пространство",
179
- "Account disabled": "Акаунтът е деактивиран"
181
+ "Account disabled": "Акаунтът е деактивиран",
182
+ "upload_name": "Качване с ново име",
183
+ "Uploading": "Качване",
184
+ "proxy_413": "Проверете за това ограничение на прокси сървъра"
180
185
  }
181
186
  }
@@ -1,7 +1,6 @@
1
1
  {
2
2
  "author": "Uwe Wennmann",
3
3
  "version": 0.1,
4
- "hfs_version": "0.57.5",
5
4
  "translate": {
6
5
  "Select": "Auswählen",
7
6
  "n_files": "{n,plural,one{# Datei} other{# Dateien}}",
@@ -39,6 +38,7 @@
39
38
  "Password": "Passwort",
40
39
  "login_untrusted": "Anmeldung abgebrochen: Der Serveridentität kann nicht vertraut werden.",
41
40
  "login_bad_credentials": "Ungültige Anmeldedaten",
41
+ "confirm_url_login": "Dieser Link versucht, Sie als „{username}“ anzumelden",
42
42
  "login_bad_cookies": "Cookies funktionieren nicht - Anmeldung fehlgeschlagen",
43
43
  "User panel": "Benutzer-Panel",
44
44
  "Change password": "Das Passwort ändern.",
@@ -110,6 +110,8 @@
110
110
  "Timestamp": "Zeitstempel",
111
111
  "Show": "Anzeigen",
112
112
  "Loading failed": "Laden fehlgeschlagen",
113
+ "Download limit reached": "Download-Limit erreicht",
114
+ "Retry": "Erneut versuchen",
113
115
  "Rename": "Umbenennen",
114
116
  "Tiles mode:": "Kachel-Modus:",
115
117
  "off": "aus",
@@ -178,6 +180,9 @@
178
180
  "No": "Nein",
179
181
  "esc_again": "Drucken Sie ESC zwei Mal, um das Fenster zu schließen.",
180
182
  "insufficient storage": "nicht genügend Speicherplatz",
181
- "Account disabled": "Konto deaktiviert"
183
+ "Account disabled": "Konto deaktiviert",
184
+ "upload_name": "Mit neuem Namen hochladen",
185
+ "Uploading": "Hochladen",
186
+ "proxy_413": "Überprüfen Sie den Proxyserver auf dieses Limit"
182
187
  }
183
188
  }
@@ -1,7 +1,6 @@
1
1
  {
2
2
  "author": "Konstantinos (konmaz)",
3
3
  "version": 1.0,
4
- "hfs_version": "0.57.5",
5
4
  "translate": {
6
5
  "Select": "Επιλογή",
7
6
  "n_files": "{n,plural,one{# αρχείο} other{# αρχεία}}",
@@ -39,6 +38,7 @@
39
38
  "Password": "Κωδικός πρόσβασης",
40
39
  "login_untrusted": "Η σύνδεση ακυρώθηκε: δεν μπορεί να εμπιστευτείται την ταυτότητα του διακομιστή",
41
40
  "login_bad_credentials": "Μη έγκυρα διαπιστευτήρια",
41
+ "confirm_url_login": "Αυτός ο σύνδεσμος προσπαθεί να σας συνδέσει ως «{username}»",
42
42
  "login_bad_cookies": "Τα cookies δεν λειτουργούν - η σύνδεση απέτυχε",
43
43
  "User panel": "Πίνακας χρήστη",
44
44
  "Change password": "Αλλαγή κωδικού πρόσβασης",
@@ -110,6 +110,8 @@
110
110
  "Timestamp": "Χρονική σήμανση",
111
111
  "Show": "Εμφάνιση",
112
112
  "Loading failed": "Η φόρτωση απέτυχε",
113
+ "Download limit reached": "Συμπληρώθηκε το όριο λήψεων",
114
+ "Retry": "Δοκιμή ξανά",
113
115
  "Rename": "Μετονομασία",
114
116
  "Tiles mode:": "Λειτουργία πλακιδίων:",
115
117
  "off": "ανενεργό",
@@ -178,6 +180,9 @@
178
180
  "No": "Οχι",
179
181
  "esc_again": "Πατήστε ESC δυο φορές για να κλειστειτε",
180
182
  "insufficient storage": "ανεπαρκής χώρος αποθήκευσης",
181
- "Account disabled": "Ο λογαριασμός έχει απενεργοποιηθεί"
183
+ "Account disabled": "Ο λογαριασμός έχει απενεργοποιηθεί",
184
+ "upload_name": "Μεταφόρτωση με νέο όνομα",
185
+ "Uploading": "Μεταφόρτωση",
186
+ "proxy_413": "Ελέγξτε για αυτό το όριο στον διακομιστή μεσολάβησης"
182
187
  }
183
188
  }