hfs 0.57.24 → 0.57.27-beta2

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 (72) hide show
  1. package/admin/assets/{index-CMArzROW.js → index-DSkhXlWX.js} +129 -129
  2. package/admin/assets/{sha512-BbjMQS_R.js → sha512-CoddZKeD.js} +1 -1
  3. package/admin/index.html +1 -1
  4. package/frontend/assets/{index-legacy-uM-kzXmI.js → index-legacy-C4Gclsbz.js} +4 -4
  5. package/frontend/assets/{sha512-legacy-CWlrw4EZ.js → sha512-legacy-C-FMk44u.js} +1 -1
  6. package/frontend/index.html +1 -1
  7. package/package.json +5 -4
  8. package/src/QuickZipStream.js +11 -8
  9. package/src/SendList.js +10 -8
  10. package/src/ThrottledStream.js +8 -6
  11. package/src/acme.js +5 -7
  12. package/src/adminApis.js +6 -7
  13. package/src/api.accounts.js +7 -9
  14. package/src/api.auth.js +7 -11
  15. package/src/api.get_file_list.js +13 -19
  16. package/src/api.lang.js +1 -1
  17. package/src/api.log.js +1 -2
  18. package/src/api.monitor.js +2 -3
  19. package/src/api.net.js +2 -3
  20. package/src/api.plugins.js +8 -10
  21. package/src/api.vfs.js +15 -20
  22. package/src/apiMiddleware.js +4 -4
  23. package/src/auth.js +6 -9
  24. package/src/basicWeb.js +1 -2
  25. package/src/block.js +1 -1
  26. package/src/commands.js +9 -9
  27. package/src/comments.js +4 -5
  28. package/src/config.js +10 -10
  29. package/src/connections.js +11 -6
  30. package/src/consoleLog.js +6 -6
  31. package/src/const.js +4 -4
  32. package/src/cross.js +19 -21
  33. package/src/customHtml.js +3 -4
  34. package/src/ddns.js +4 -4
  35. package/src/debounceAsync.js +3 -3
  36. package/src/errorPages.js +2 -3
  37. package/src/events.js +7 -10
  38. package/src/fileAttr.js +12 -8
  39. package/src/first.js +13 -8
  40. package/src/frontEndApis.js +5 -5
  41. package/src/geo.js +5 -7
  42. package/src/github.js +14 -17
  43. package/src/i18n.js +3 -4
  44. package/src/icons.js +1 -1
  45. package/src/index.js +2 -3
  46. package/src/lang.js +5 -6
  47. package/src/listen.js +25 -28
  48. package/src/log.js +20 -20
  49. package/src/middlewares.js +12 -15
  50. package/src/misc.js +15 -14
  51. package/src/nat.js +14 -16
  52. package/src/outboundProxy.js +7 -7
  53. package/src/perm.js +12 -13
  54. package/src/persistence.js +1 -1
  55. package/src/plugins.js +62 -73
  56. package/src/roots.js +1 -2
  57. package/src/selfCheck.js +5 -4
  58. package/src/serveFile.js +7 -8
  59. package/src/serveGuiAndSharedFiles.js +7 -9
  60. package/src/serveGuiFiles.js +11 -21
  61. package/src/stat.js +40 -0
  62. package/src/statWorker.js +11 -0
  63. package/src/throttler.js +5 -7
  64. package/src/update.js +9 -9
  65. package/src/upload.js +11 -13
  66. package/src/util-files.js +48 -32
  67. package/src/util-http.js +80 -29
  68. package/src/util-os.js +5 -8
  69. package/src/vfs.js +77 -68
  70. package/src/walkDir.js +9 -9
  71. package/src/watchLoad.js +4 -4
  72. package/src/zip.js +5 -10
package/src/github.js CHANGED
@@ -39,15 +39,13 @@ function getGithubDefaultBranch(repo) {
39
39
  if (!repo.includes('/'))
40
40
  throw 'malformed repo';
41
41
  return branchCache.try(repo, async () => {
42
- var _a;
43
42
  for (const b of ['main', 'master']) // try to not consume api quota
44
43
  if (await (0, misc_1.httpString)(`https://github.com/${repo}/raw/refs/heads/${b}/dist/plugin.js`, { method: 'HEAD', noRedirect: true }).then(() => 1, () => 0))
45
44
  return b;
46
- return (_a = (await getRepoInfo(repo))) === null || _a === void 0 ? void 0 : _a.default_branch;
45
+ return (await getRepoInfo(repo))?.default_branch;
47
46
  });
48
47
  }
49
48
  async function downloadPlugin(repo, { branch = '', overwrite = false } = {}) {
50
- var _a, _b, _c;
51
49
  if (typeof repo !== 'string')
52
50
  repo = repo.main;
53
51
  if (exports.downloading[repo])
@@ -63,19 +61,19 @@ async function downloadPlugin(repo, { branch = '', overwrite = false } = {}) {
63
61
  if (customRepo) { // custom repo
64
62
  if (!pl)
65
63
  throw new apiMiddleware_1.ApiError(const_1.HTTP_BAD_REQUEST, "bad repo");
66
- const customRepo = (((_b = (_a = pl).getData) === null || _b === void 0 ? void 0 : _b.call(_a)) || pl).repo;
67
- let url = customRepo === null || customRepo === void 0 ? void 0 : customRepo.zip;
64
+ const customRepo = (pl.getData?.() || pl).repo;
65
+ let url = customRepo?.zip;
68
66
  if (!url)
69
67
  throw new apiMiddleware_1.ApiError(const_1.HTTP_SERVER_ERROR, "bad plugin");
70
68
  if (!url.includes('//'))
71
69
  url = customRepo.web + url;
72
- return await go(url, pl === null || pl === void 0 ? void 0 : pl.id, (_c = customRepo.zipRoot) !== null && _c !== void 0 ? _c : DIST_ROOT);
70
+ return await go(url, pl?.id, customRepo.zipRoot ?? DIST_ROOT);
73
71
  }
74
- branch || (branch = await getGithubDefaultBranch(repo));
72
+ branch ||= await getGithubDefaultBranch(repo);
75
73
  const short = repo.split('/')[1]; // second part, repo without the owner
76
74
  if (!short)
77
75
  throw new apiMiddleware_1.ApiError(const_1.HTTP_BAD_REQUEST, "bad repo");
78
- const folder = overwrite && (pl === null || pl === void 0 ? void 0 : pl.id) // use existing folder
76
+ const folder = overwrite && pl?.id // use existing folder
79
77
  || (getFolder2repo().hasOwnProperty(short) ? repo.replace('/', '-') // longer form only if another plugin is using short form, to avoid overwriting
80
78
  : short.replace(/^hfs-/, ''));
81
79
  const GITHUB_ZIP_ROOT = short + '-' + branch; // GitHub puts everything within this folder
@@ -137,7 +135,7 @@ async function downloadPlugin(repo, { branch = '', overwrite = false } = {}) {
137
135
  await (0, promises_1.rename)(tempInstallPath, installPath)
138
136
  .catch(e => { throw e.code !== 'ENOENT' ? e : new apiMiddleware_1.ApiError(const_1.HTTP_NOT_ACCEPTABLE, "missing main file"); });
139
137
  if (wasRunning)
140
- if (await (0, misc_1.waitFor)(() => (0, plugins_1.getPluginInfo)(folder), { timeout: 10000 }))
138
+ if (await (0, misc_1.waitFor)(() => (0, plugins_1.getPluginInfo)(folder), { timeout: 10_000 }))
141
139
  void (0, plugins_1.startPlugin)(folder) // don't wait, in case it fails to start. We still use startPlugin instead of enablePlugin, as it will take care of disabling other themes.
142
140
  .catch(console.warn);
143
141
  events_1.default.emit('pluginDownloaded', { id: folder, repo });
@@ -167,7 +165,7 @@ async function readOnlinePlugin(repo, branch = '') {
167
165
  main = pl.repo.web + main;
168
166
  return (0, plugins_1.parsePluginSource)(main, await (0, misc_1.httpString)(main)); // use 'repo' as 'id' client-side
169
167
  }
170
- branch || (branch = await getGithubDefaultBranch(repo));
168
+ branch ||= await getGithubDefaultBranch(repo);
171
169
  const res = await readGithubFile(`${repo}/${branch}/${DIST_ROOT}/${plugins_1.PLUGIN_MAIN_FILE}`);
172
170
  const pl = (0, plugins_1.parsePluginSource)(repo, res); // use 'repo' as 'id' client-side
173
171
  pl.branch = branch;
@@ -175,13 +173,13 @@ async function readOnlinePlugin(repo, branch = '') {
175
173
  }
176
174
  async function readOnlineCompatiblePlugin(repo, branch = '') {
177
175
  const pl = await readOnlinePlugin(repo, branch);
178
- if (!(pl === null || pl === void 0 ? void 0 : pl.apiRequired))
176
+ if (!pl?.apiRequired)
179
177
  return; // mandatory field
180
178
  if (!pl.badApi)
181
179
  return pl;
182
180
  // we try other branches (starting with 'api')
183
181
  const res = await apiGithub('repos/' + repo + '/branches');
184
- const branches = res.map((x) => x === null || x === void 0 ? void 0 : x.name)
182
+ const branches = res.map((x) => x?.name)
185
183
  .filter((x) => typeof x === 'string' && x.startsWith('api'))
186
184
  .sort().reverse();
187
185
  for (const branch of branches) {
@@ -234,7 +232,7 @@ async function* apiGithubPaginated(uri) {
234
232
  }
235
233
  }
236
234
  async function isPluginBlacklisted(repo) {
237
- return (0, exports.getProjectInfo)().then(x => { var _a, _b; return ((_b = (_a = x === null || x === void 0 ? void 0 : x.repo_blacklist) === null || _a === void 0 ? void 0 : _a[repo]) === null || _b === void 0 ? void 0 : _b.message) || ''; }, () => undefined);
235
+ return (0, exports.getProjectInfo)().then(x => x?.repo_blacklist?.[repo]?.message || '', () => undefined);
238
236
  }
239
237
  async function searchPlugins(text = '', { skipRepos = [''] } = {}) {
240
238
  // github doesn't allow complex search, so we have to do it multiple times and merge the results
@@ -245,7 +243,6 @@ async function searchPlugins(text = '', { skipRepos = [''] } = {}) {
245
243
  const list = await Promise.all(searches.map(x => (0, misc_1.asyncGeneratorToArray)(apiGithubPaginated(`search/repositories?q=topic:hfs-plugin+${x}`))));
246
244
  const deduped = lodash_1.default.uniqBy(list.flat(), x => x.full_name);
247
245
  return new misc_1.AsapStream(deduped.map(async (it) => {
248
- var _a;
249
246
  const repo = it.full_name;
250
247
  if (skipRepos.includes(repo) || await isPluginBlacklisted(repo))
251
248
  return;
@@ -255,7 +252,7 @@ async function searchPlugins(text = '', { skipRepos = [''] } = {}) {
255
252
  Object.assign(pl, {
256
253
  repo, // overwrite parsed value, that may be wrong
257
254
  downloading: exports.downloading[repo],
258
- license: (_a = it.license) === null || _a === void 0 ? void 0 : _a.spdx_id,
255
+ license: it.license?.spdx_id,
259
256
  }, lodash_1.default.pick(it, ['pushed_at', 'stargazers_count', 'default_branch']));
260
257
  return pl;
261
258
  }));
@@ -273,7 +270,7 @@ exports.getProjectInfo = (0, misc_1.debounceAsync)(() => argv_1.argv.central ===
273
270
  .then(o => {
274
271
  if (o)
275
272
  cachedCentralInfo.set(o);
276
- o || (o = { ...cachedCentralInfo.get() || builtIn }); // fall back to built-in
273
+ o ||= { ...cachedCentralInfo.get() || builtIn }; // fall back to built-in
277
274
  // merge byVersions info in the main object, but collect alerts separately, to preserve multiple instances
278
275
  const allAlerts = [o.alert];
279
276
  for (const [ver, more] of Object.entries((0, misc_1.popKey)(o, 'byVersion') || {}))
@@ -296,4 +293,4 @@ exports.getProjectInfo = (0, misc_1.debounceAsync)(() => argv_1.argv.central ===
296
293
  (0, plugins_1.enablePlugin)(p.id, false);
297
294
  }
298
295
  return o;
299
- }), { retain: misc_1.HOUR, retainFailure: 60000 });
296
+ }), { retain: misc_1.HOUR, retainFailure: 60_000 });
package/src/i18n.js CHANGED
@@ -21,11 +21,10 @@ function i18nFromTranslations(translations, embedded = 'en') {
21
21
  }
22
22
  // If one of the keys is an "id", that should be the first. If one of the keys should work as a fallback, that should be the last. Use 'fallback' parameter if you don't want the fallback to work as a key.
23
23
  function t(keyOrTpl, params, fallback) {
24
- var _a, _b;
25
24
  if (!keyOrTpl)
26
25
  return '';
27
26
  // memoize?
28
- const keys = isTemplateStringsArray(keyOrTpl) ? [(fallback !== null && fallback !== void 0 ? fallback : (fallback = keyOrTpl[0]))]
27
+ const keys = isTemplateStringsArray(keyOrTpl) ? [(fallback ??= keyOrTpl[0])]
29
28
  : Array.isArray(keyOrTpl) ? keyOrTpl : [keyOrTpl];
30
29
  if (typeof params === 'string' && !fallback) {
31
30
  fallback = params;
@@ -38,7 +37,7 @@ function i18nFromTranslations(translations, embedded = 'en') {
38
37
  if (!state.disabled)
39
38
  for (const key of keys) {
40
39
  for (const lang of searchLangs)
41
- if (found = (_b = (_a = state.translations[selectedLang = lang]) === null || _a === void 0 ? void 0 : _a.translate) === null || _b === void 0 ? void 0 : _b[key])
40
+ if (found = state.translations[selectedLang = lang]?.translate?.[key])
42
41
  break;
43
42
  if (found)
44
43
  break;
@@ -111,7 +110,7 @@ function i18nFromTranslations(translations, embedded = 'en') {
111
110
  yield [s.slice(ofs), false];
112
111
  }
113
112
  function isTemplateStringsArray(x) {
114
- return (x === null || x === void 0 ? void 0 : x.raw) && Array.isArray(x);
113
+ return x?.raw && Array.isArray(x);
115
114
  }
116
115
  return {
117
116
  t,
package/src/icons.js CHANGED
@@ -24,7 +24,7 @@ function watchIconsFolder(parentFolder, cb) {
24
24
  }
25
25
  cb(res);
26
26
  }
27
- catch (_a) {
27
+ catch {
28
28
  cb(undefined);
29
29
  } // no such dir
30
30
  }), true);
package/src/index.js CHANGED
@@ -4,7 +4,6 @@
4
4
  var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  return (mod && mod.__esModule) ? mod : { "default": mod };
6
6
  };
7
- var _a;
8
7
  Object.defineProperty(exports, "__esModule", { value: true });
9
8
  exports.app = void 0;
10
9
  const koa_1 = __importDefault(require("koa"));
@@ -39,7 +38,7 @@ if (new config_1.Version(process.versions.node).olderThan('18.15.0')) {
39
38
  process.exit(2);
40
39
  }
41
40
  process.title = 'HFS ' + const_1.VERSION;
42
- const keys = ((_a = process.env.COOKIE_SIGN_KEYS) === null || _a === void 0 ? void 0 : _a.split(','))
41
+ const keys = process.env.COOKIE_SIGN_KEYS?.split(',')
43
42
  || [(0, misc_1.randomId)(30)]; // randomness at start gives some extra security, btu also invalidates existing sessions
44
43
  exports.app = new koa_1.default({ keys });
45
44
  exports.app.use(middlewares_1.sessionMiddleware)
@@ -78,7 +77,7 @@ process.on('uncaughtException', (err) => {
78
77
  try {
79
78
  console.error("uncaught:", err);
80
79
  }
81
- catch (_a) { } // in case we are writing to a closed terminal, we may throw with "write eio at afterwritedispatched", causing an infinite loop
80
+ catch { } // in case we are writing to a closed terminal, we may throw with "write eio at afterwritedispatched", causing an infinite loop
82
81
  });
83
82
  // this warning is scaring users, and has been removed in node 20.12.0 https://github.com/nodejs/node/pull/51204
84
83
  const original = process.emitWarning;
package/src/lang.js CHANGED
@@ -24,7 +24,7 @@ function code2file(code) {
24
24
  function file2code(fn) {
25
25
  return fn.replace(PREFIX, '').replace(SUFFIX, '');
26
26
  }
27
- const cache = (0, expiringCache_1.expiringCache)(3000); // 3 seconds for both a good dx and acceptable performance
27
+ const cache = (0, expiringCache_1.expiringCache)(3_000); // 3 seconds for both a good dx and acceptable performance
28
28
  async function getLangData(ctxOrLangCsv) {
29
29
  if (typeof ctxOrLangCsv !== 'string') {
30
30
  const ctx = ctxOrLangCsv;
@@ -35,7 +35,6 @@ async function getLangData(ctxOrLangCsv) {
35
35
  }
36
36
  const csv = ctxOrLangCsv.toLowerCase();
37
37
  return cache.try(csv, async () => {
38
- var _a;
39
38
  const langs = csv.split(',');
40
39
  const ret = {};
41
40
  let i = 0;
@@ -47,7 +46,7 @@ async function getLangData(ctxOrLangCsv) {
47
46
  try {
48
47
  ret[k] = JSON.parse(await (0, promises_1.readFile)(fn, 'utf8'));
49
48
  } // allow external files to override embedded translations
50
- catch (_b) {
49
+ catch {
51
50
  if ((0, misc_1.hasProp)(embedded_1.default, k))
52
51
  ret[k] = lodash_1.default.cloneDeep(embedded_1.default[k]);
53
52
  else {
@@ -60,9 +59,9 @@ async function getLangData(ctxOrLangCsv) {
60
59
  }
61
60
  }
62
61
  }
63
- const fromPlugins = (0, misc_1.onlyTruthy)(await Promise.all((0, plugins_1.mapPlugins)(async (pl) => { var _a; return (_a = (0, misc_1.tryJson)(await (0, promises_1.readFile)((0, path_1.join)(pl.folder, fn), 'utf8').catch(() => ''))) === null || _a === void 0 ? void 0 : _a.translate; })));
62
+ const fromPlugins = (0, misc_1.onlyTruthy)(await Promise.all((0, plugins_1.mapPlugins)(async (pl) => (0, misc_1.tryJson)(await (0, promises_1.readFile)((0, path_1.join)(pl.folder, fn), 'utf8').catch(() => ''))?.translate)));
64
63
  if (fromPlugins.length)
65
- lodash_1.default.defaults((_a = (ret[k] || (ret[k] = {}))).translate || (_a.translate = {}), ...fromPlugins); // be sure we have an entry for k
64
+ lodash_1.default.defaults((ret[k] ||= {}).translate ||= {}, ...fromPlugins); // be sure we have an entry for k
66
65
  i++;
67
66
  }
68
67
  return ret;
@@ -71,7 +70,7 @@ async function getLangData(ctxOrLangCsv) {
71
70
  let forceLangData;
72
71
  let undo;
73
72
  (0, config_1.defineConfig)(misc_1.CFG.force_lang, '', v => {
74
- undo === null || undo === void 0 ? void 0 : undo();
73
+ undo?.();
75
74
  if (!v)
76
75
  return forceLangData = undefined;
77
76
  const translation = embedded_1.default[v];
package/src/listen.js CHANGED
@@ -70,17 +70,19 @@ const consoleLog_1 = require("./consoleLog");
70
70
  let httpSrv;
71
71
  let httpsSrv;
72
72
  const openBrowserAtStart = (0, config_1.defineConfig)('open_browser_at_start', true);
73
- exports.baseUrl = (0, config_1.defineConfig)(misc_1.CFG.base_url, '', x => { var _a; return (_a = /(?<=\/\/)[^\/]+/.exec(x)) === null || _a === void 0 ? void 0 : _a[0]; }); // compiled is host only
73
+ exports.baseUrl = (0, config_1.defineConfig)(misc_1.CFG.base_url, '', x => /(?<=\/\/)[^\/]+/.exec(x)?.[0]); // compiled is host only
74
74
  async function getBaseUrlOrDefault() {
75
75
  return exports.baseUrl.get() || await nat_1.defaultBaseUrl.get();
76
76
  }
77
77
  function getHttpsWorkingPort() {
78
- var _a;
79
- return (httpsSrv === null || httpsSrv === void 0 ? void 0 : httpsSrv.listening) && ((_a = httpsSrv.address()) === null || _a === void 0 ? void 0 : _a.port);
78
+ return httpsSrv?.listening && httpsSrv.address()?.port;
80
79
  }
81
- const commonServerOptions = { requestTimeout: 0 };
80
+ const commonServerOptions = {
81
+ requestTimeout: 0, // disable timeout on long uploads
82
+ maxHeaderSize: 32768, // allow a larger header for a larger list in ?get=zip
83
+ };
82
84
  // these are properties that can be assigned to the server object
83
- const commonServerAssign = { headersTimeout: 30000, timeout: misc_1.MINUTE }; // 'headersTimeout' is not recognized by type lib, and 'timeout' is not effective when passed in parameters
85
+ const commonServerAssign = { headersTimeout: 30_000, timeout: misc_1.MINUTE }; // 'headersTimeout' is not recognized by type lib, and 'timeout' is not effective when passed in parameters
84
86
  const readyToListen = Promise.all([persistence_1.storedMap.isOpening(), events_1.default.once('app')]);
85
87
  const considerHttp = (0, misc_1.debounceAsync)(async () => {
86
88
  await readyToListen;
@@ -105,7 +107,7 @@ const listenInterface = (0, config_1.defineConfig)('listen_interface', '');
105
107
  (0, config_1.subMultipleConfigs)(considerHttp, [exports.portCfg, listenInterface]);
106
108
  function openAdmin() {
107
109
  for (const srv of [httpSrv, httpsSrv]) {
108
- const a = srv === null || srv === void 0 ? void 0 : srv.address();
110
+ const a = srv?.address();
109
111
  if (!a || typeof a === 'string')
110
112
  continue;
111
113
  const i = listenInterface.get();
@@ -123,21 +125,19 @@ function openAdmin() {
123
125
  console.log("openAdmin failed");
124
126
  }
125
127
  function getCertObject() {
126
- var _a;
127
128
  const c = exports.cert.compiled();
128
129
  if (!c)
129
130
  return;
130
131
  const all = new crypto_1.X509Certificate(c);
131
132
  const some = lodash_1.default.pick(all, ['subject', 'issuer', 'validFrom', 'validTo']);
132
- const ret = (0, misc_1.objSameKeys)(some, v => (v === null || v === void 0 ? void 0 : v.includes('=')) ? Object.fromEntries(v.split('\n').map(x => x.split('='))) : v);
133
- return Object.assign(ret, { altNames: (_a = all.subjectAltName) === null || _a === void 0 ? void 0 : _a.replace(/DNS:/g, '').split(/, */) });
133
+ const ret = (0, misc_1.objSameKeys)(some, v => v?.includes('=') ? Object.fromEntries(v.split('\n').map(x => x.split('='))) : v);
134
+ return Object.assign(ret, { altNames: all.subjectAltName?.replace(/DNS:/g, '').split(/, */) });
134
135
  }
135
136
  const considerHttps = (0, misc_1.debounceAsync)(async () => {
136
- var _a, _b, _c, _d;
137
137
  await readyToListen;
138
138
  void stopServer(httpsSrv);
139
139
  nat_1.defaultBaseUrl.proto = 'http';
140
- nat_1.defaultBaseUrl.port = (_a = getCurrentPort(httpSrv)) !== null && _a !== void 0 ? _a : 0;
140
+ nat_1.defaultBaseUrl.port = getCurrentPort(httpSrv) ?? 0;
141
141
  let port = exports.httpsPortCfg.get();
142
142
  try {
143
143
  const moreOptions = Object.assign({}, ...await events_1.default.emitAsync('httpsServerOptions') || []); // emitAsync returns an array of objects
@@ -150,9 +150,9 @@ const considerHttps = (0, misc_1.debounceAsync)(async () => {
150
150
  if (port >= 0) {
151
151
  const certObj = getCertObject();
152
152
  if (certObj) {
153
- const cn = (_b = certObj.subject) === null || _b === void 0 ? void 0 : _b.CN;
153
+ const cn = certObj.subject?.CN;
154
154
  if (cn)
155
- console.log("certificate loaded for", ((_c = certObj.altNames) === null || _c === void 0 ? void 0 : _c.join(' + ')) || cn);
155
+ console.log("certificate loaded for", certObj.altNames?.join(' + ') || cn);
156
156
  const now = new Date();
157
157
  const from = new Date(certObj.validFrom);
158
158
  const to = new Date(certObj.validTo);
@@ -178,7 +178,7 @@ const considerHttps = (0, misc_1.debounceAsync)(async () => {
178
178
  }
179
179
  }
180
180
  catch (e) {
181
- httpsSrv || (httpsSrv = Object.assign(https.createServer({}), { name: 'https' })); // a dummy container, in case creation failed because of certificate errors
181
+ httpsSrv ||= Object.assign(https.createServer({}), { name: 'https' }); // a dummy container, in case creation failed because of certificate errors
182
182
  httpsSrv.error = "bad private key or certificate";
183
183
  console.error("failed to create https server: check your private key and certificate", e.message);
184
184
  return;
@@ -196,14 +196,13 @@ const considerHttps = (0, misc_1.debounceAsync)(async () => {
196
196
  printUrls(httpsSrv.name);
197
197
  events_1.default.emit('httpsReady');
198
198
  nat_1.defaultBaseUrl.proto = 'https';
199
- nat_1.defaultBaseUrl.port = (_d = getCurrentPort(httpsSrv)) !== null && _d !== void 0 ? _d : 0;
199
+ nat_1.defaultBaseUrl.port = getCurrentPort(httpsSrv) ?? 0;
200
200
  }, { wait: 200 }); // give time to have key and cert ready
201
201
  exports.cert = (0, config_1.defineConfig)('cert', '', load);
202
202
  exports.privateKey = (0, config_1.defineConfig)('private_key', '', load);
203
203
  const httpsNeeds = [exports.cert, exports.privateKey];
204
204
  function load(v, { object }) {
205
- var _a;
206
- (_a = object.watcher) === null || _a === void 0 ? void 0 : _a.unwatch();
205
+ object.watcher?.unwatch();
207
206
  if (!v || v.includes('\n'))
208
207
  return v;
209
208
  // v is a path, we'll watch the file for changes
@@ -244,12 +243,12 @@ function startServer(srv, { port, host }) {
244
243
  });
245
244
  async function testIpV4() {
246
245
  const res = await listen('0.0.0.0', true);
247
- await new Promise(res => srv === null || srv === void 0 ? void 0 : srv.close(res)); // close, if any, and wait
246
+ await new Promise(res => srv?.close(res)); // close, if any, and wait
248
247
  return res > 0;
249
248
  }
250
249
  function listen(host, silence = false) {
251
250
  return new Promise(async (resolve, reject) => {
252
- srv === null || srv === void 0 ? void 0 : srv.on('error', onError).listen({ port, host }, () => {
251
+ srv?.on('error', onError).listen({ port, host }, () => {
253
252
  const ad = srv.address();
254
253
  if (!ad)
255
254
  return reject('no address');
@@ -268,7 +267,7 @@ function startServer(srv, { port, host }) {
268
267
  srv.busy = undefined;
269
268
  const { code } = e;
270
269
  if (code)
271
- srv.busy = (0, find_process_1.default)('port', port).then(res => res === null || res === void 0 ? void 0 : res.map(x => { var _a; return (0, misc_1.prefix)("Service", x.name === 'svchost.exe' && ((_a = x.cmd.split(x.name)[1]) === null || _a === void 0 ? void 0 : _a.trim())) || x.name; }).join(' + '), () => '');
270
+ srv.busy = (0, find_process_1.default)('port', port).then(res => res?.map(x => (0, misc_1.prefix)("Service", x.name === 'svchost.exe' && x.cmd.split(x.name)[1]?.trim()) || x.name).join(' + '), () => '');
272
271
  if (code === 'EACCES' && port < 1024 && !srv.busy) // on Windows, when port is used by a service, we get EACCES
273
272
  srv.error = `lacking permission on port ${port}, try with permission (${const_1.IS_WINDOWS ? 'administrator' : 'sudo'}) or port > 1024`;
274
273
  if (code === 'EADDRINUSE' || srv.busy)
@@ -282,7 +281,7 @@ function startServer(srv, { port, host }) {
282
281
  }
283
282
  function stopServer(srv) {
284
283
  return new Promise(resolve => {
285
- if (!(srv === null || srv === void 0 ? void 0 : srv.listening))
284
+ if (!srv?.listening)
286
285
  return resolve(null);
287
286
  const ad = srv.address();
288
287
  if (ad && typeof ad !== 'string')
@@ -295,8 +294,7 @@ function stopServer(srv) {
295
294
  });
296
295
  }
297
296
  function getCurrentPort(srv) {
298
- var _a;
299
- return (_a = srv === null || srv === void 0 ? void 0 : srv.address()) === null || _a === void 0 ? void 0 : _a.port;
297
+ return srv?.address()?.port;
300
298
  }
301
299
  async function getServerStatus(includeSrv = true) {
302
300
  return {
@@ -304,7 +302,7 @@ async function getServerStatus(includeSrv = true) {
304
302
  https: await serverStatus(httpsSrv, exports.httpsPortCfg.get()),
305
303
  };
306
304
  async function serverStatus(srv, configuredPort) {
307
- const busy = await (srv === null || srv === void 0 ? void 0 : srv.busy);
305
+ const busy = await srv?.busy;
308
306
  await (0, misc_1.wait)(0); // simple trick to wait for also .error to be updated. If this trickery becomes necessary elsewhere, then we should make also error a Promise.
309
307
  return {
310
308
  ...lodash_1.default.pick(srv, ['listening', 'error']),
@@ -329,17 +327,16 @@ async function getIps(external = true) {
329
327
  x => x !== nat_1.defaultBaseUrl.localIp, // use the "nat" info to put best ip first
330
328
  net_1.isIPv6 // false=IPV4 comes first
331
329
  ]);
332
- nat_1.defaultBaseUrl.localIp || (nat_1.defaultBaseUrl.localIp = ret[0] || '');
330
+ nat_1.defaultBaseUrl.localIp ||= ret[0] || '';
333
331
  return ret;
334
332
  }
335
333
  async function getUrls() {
336
334
  const on = listenInterface.get();
337
335
  const ips = on === renderHost(on) ? [on] : await getIps();
338
336
  return Object.fromEntries((0, misc_1.onlyTruthy)([httpSrv, httpsSrv].map(srv => {
339
- var _a;
340
- if (!(srv === null || srv === void 0 ? void 0 : srv.listening))
337
+ if (!srv?.listening)
341
338
  return false;
342
- const port = (_a = srv === null || srv === void 0 ? void 0 : srv.address()) === null || _a === void 0 ? void 0 : _a.port;
339
+ const port = srv?.address()?.port;
343
340
  const appendPort = port === (srv.name === 'https' ? 443 : 80) ? '' : ':' + port;
344
341
  const urls = ips.map(ip => `${srv.name}://${(0, misc_1.ipForUrl)(ip)}${appendPort}`);
345
342
  return urls.length && [srv.name, urls];
package/src/log.js CHANGED
@@ -43,7 +43,6 @@ const events_1 = require("events");
43
43
  const config_1 = require("./config");
44
44
  const fs_1 = require("fs");
45
45
  const util = __importStar(require("util"));
46
- const promises_1 = require("fs/promises");
47
46
  const lodash_1 = __importDefault(require("lodash"));
48
47
  const util_files_1 = require("./util-files");
49
48
  const auth_1 = require("./auth");
@@ -55,30 +54,32 @@ const index_1 = require("./index");
55
54
  const serveGuiFiles_1 = require("./serveGuiFiles");
56
55
  const fast_glob_1 = __importDefault(require("fast-glob"));
57
56
  class Logger {
57
+ name;
58
+ stream;
59
+ last;
60
+ path = '';
58
61
  constructor(name) {
59
62
  this.name = name;
60
- this.path = '';
61
63
  }
62
64
  async setPath(path) {
63
- var _a;
64
65
  this.path = path;
65
- (_a = this.stream) === null || _a === void 0 ? void 0 : _a.end();
66
+ this.stream?.end();
66
67
  this.last = undefined;
67
68
  if (!path)
68
69
  return this.stream = undefined;
69
70
  try {
70
- const stats = await (0, promises_1.stat)(path);
71
+ const stats = await (0, util_files_1.statWithTimeout)(path);
71
72
  this.last = stats.mtime;
72
73
  }
73
- catch (_b) {
74
- if (await (0, util_files_1.prepareFolder)(path) === false)
74
+ catch {
75
+ if (await (0, util_files_1.ensureParentFolder)(path) === false)
75
76
  console.log("cannot create folder for", path);
76
77
  }
77
78
  this.reopen();
78
79
  }
79
80
  reopen() {
80
- var _a;
81
- return this.stream = (_a = (0, util_files_1.createFileWithPath)(this.path, { flags: 'a' })) === null || _a === void 0 ? void 0 : _a.on('error', () => this.stream = undefined);
81
+ return this.stream = (0, util_files_1.createFileWithPath)(this.path, { flags: 'a' })
82
+ ?.on('error', () => this.stream = undefined);
82
83
  }
83
84
  }
84
85
  // we'll have names same as config keys. These are used also by the get_log api.
@@ -116,21 +117,20 @@ const logMw = async (ctx, next) => {
116
117
  const conn = (0, connections_1.getConnection)(ctx); // collect reference before close
117
118
  // don't await, as we don't want to hold the middlewares chain
118
119
  ctx.state.completed.then(() => {
119
- var _a, _b, _c, _d;
120
120
  if (ctx.state.dontLog || ctx.state.considerAsGui && !serveGuiFiles_1.logGui.get())
121
121
  return;
122
122
  if (dontLogNet.compiled()(ctx.ip))
123
123
  return;
124
124
  const isError = ctx.status >= 400;
125
125
  const logger = isError && accessErrorLog || accessLogger;
126
- const rotate = (_a = logRotation.get()) === null || _a === void 0 ? void 0 : _a[0];
126
+ const rotate = logRotation.get()?.[0];
127
127
  let { stream, last, path } = logger;
128
128
  if (!stream)
129
129
  return;
130
130
  logger.last = now;
131
131
  if (rotate && last) { // rotation enabled and a file exists?
132
132
  const passed = Number(now) - Number(last)
133
- - 3600000; // be pessimistic and count a possible DST change
133
+ - 3600_000; // be pessimistic and count a possible DST change
134
134
  if (rotate === 'm' && (passed >= 31 * misc_1.DAY || now.getMonth() !== last.getMonth())
135
135
  || rotate === 'd' && (passed >= misc_1.DAY || now.getDate() !== last.getDate()) // checking passed will solve the case when the day of the month is the same but a month has passed
136
136
  || rotate === 'w' && (passed >= 7 * misc_1.DAY || now.getDay() < last.getDay())) {
@@ -150,9 +150,9 @@ const logMw = async (ctx, next) => {
150
150
  }
151
151
  const format = '%s - %s [%s] "%s %s HTTP/%s" %d %s %s\n'; // Apache's Common Log Format
152
152
  const a = now.toString().split(' '); // like nginx, our default log contains the time of log writing
153
- const date = a[2] + '/' + a[1] + '/' + a[3] + ':' + a[4] + ' ' + ((_b = a[5]) === null || _b === void 0 ? void 0 : _b.slice(3));
153
+ const date = a[2] + '/' + a[1] + '/' + a[3] + ':' + a[4] + ' ' + a[5]?.slice(3);
154
154
  const user = (0, auth_1.getCurrentUsername)(ctx);
155
- const length = (_c = ctx.state.length) !== null && _c !== void 0 ? _c : ctx.length;
155
+ const length = ctx.state.length ?? ctx.length;
156
156
  const uri = ctx.originalUrl;
157
157
  const duration = (Date.now() - Number(now)) / 1000;
158
158
  ctx.logExtra(ctx.vfsNode && {
@@ -163,7 +163,7 @@ const logMw = async (ctx, next) => {
163
163
  size: ctx.state.uploadSize,
164
164
  speed: Math.round(ctx.state.uploadSize / duration),
165
165
  });
166
- if (conn === null || conn === void 0 ? void 0 : conn.country)
166
+ if (conn?.country)
167
167
  ctx.logExtra({ country: conn.country });
168
168
  if (logUA.get())
169
169
  ctx.logExtra({ ua: ctx.get('user-agent') || undefined });
@@ -171,8 +171,8 @@ const logMw = async (ctx, next) => {
171
171
  if (events_2.default.anyListener(logger.name)) // small optimization: this event can happen often, while most times there's no listener, and the parameters object is constructed pointlessly. A benchmark measured it 20% faster (just the line), while maybe it was not necessary.
172
172
  events_2.default.emit(logger.name, { ctx, length, user, ts: now, uri, extra });
173
173
  debounce(() => // once in a while we check if the file is still good (not deleted, etc), or we'll reopen it
174
- (0, promises_1.stat)(logger.path).catch(() => logger.reopen())); // async = smoother but we may lose some entries
175
- stream.write(util.format(format, ctx.ip, user || '-', date, ctx.method, uri, ctx.req.httpVersion, ctx.status, (_d = length === null || length === void 0 ? void 0 : length.toString()) !== null && _d !== void 0 ? _d : '-', lodash_1.default.isEmpty(extra) ? '' : JSON.stringify(JSON.stringify(extra))));
174
+ (0, util_files_1.statWithTimeout)(logger.path).catch(() => logger.reopen())); // async = smoother but we may lose some entries
175
+ stream.write(util.format(format, ctx.ip, user || '-', date, ctx.method, uri, ctx.req.httpVersion, ctx.status, length?.toString() ?? '-', lodash_1.default.isEmpty(extra) ? '' : JSON.stringify(JSON.stringify(extra))));
176
176
  });
177
177
  };
178
178
  exports.logMw = logMw;
@@ -187,7 +187,7 @@ function doubleDigit(n) {
187
187
  async function getRotatedFiles() {
188
188
  return Object.fromEntries(await Promise.all(exports.loggers.map(async (x) => {
189
189
  const mask = (0, misc_1.strinsert)(x.path, x.path.length - (0, path_1.extname)(x.path).length, '-2*'); // including 2, initial digit of the year, will only take rotated files and not "-error"
190
- return [x.name, (await (0, fast_glob_1.default)(mask, { stats: true })).map(x => { var _a; return ({ path: x.path, size: (_a = x.stats) === null || _a === void 0 ? void 0 : _a.size }); })];
190
+ return [x.name, (await (0, fast_glob_1.default)(mask, { stats: true })).map(x => ({ path: x.path, size: x.stats?.size }))];
191
191
  })));
192
192
  }
193
193
  // dump console.error to file
@@ -196,11 +196,11 @@ debugLogFile.once('open', () => {
196
196
  const was = console.error;
197
197
  console.error = function (...args) {
198
198
  was.apply(this, args);
199
- args = args.map(x => { var _a; return typeof x === 'string' ? x : ((_a = (0, misc_1.tryJson)(x)) !== null && _a !== void 0 ? _a : String(x)); });
199
+ args = args.map(x => typeof x === 'string' ? x : ((0, misc_1.tryJson)(x) ?? String(x)));
200
200
  debugLogFile.write((0, misc_1.formatTimestamp)(new Date) + ' - ' + args.join(' ') + '\n');
201
201
  };
202
202
  // limit log size
203
- const LIMIT = 1000000;
203
+ const LIMIT = 1_000_000;
204
204
  const { path } = debugLogFile;
205
205
  (0, misc_1.repeat)(misc_1.DAY, () => {
206
206
  if ((0, fs_1.statSync)(path).size < LIMIT)
@@ -53,7 +53,7 @@ let proxyDetected;
53
53
  const someSecurity = (ctx, next) => {
54
54
  ctx.request.ip = (0, connections_1.normalizeIp)(ctx.ip);
55
55
  const ss = ctx.session;
56
- if ((ss === null || ss === void 0 ? void 0 : ss.username) && !(ss === null || ss === void 0 ? void 0 : ss[misc_1.ALLOW_SESSION_IP_CHANGE]))
56
+ if (ss?.username && !ss?.[misc_1.ALLOW_SESSION_IP_CHANGE])
57
57
  if (!ss.ip)
58
58
  ss.ip = ctx.ip;
59
59
  else if (ss.ip !== ctx.ip) {
@@ -61,7 +61,7 @@ const someSecurity = (ctx, next) => {
61
61
  ss.ip = ctx.ip;
62
62
  }
63
63
  try {
64
- if ((0, misc_1.dirTraversal)(decodeURI(ctx.path)))
64
+ if ((0, misc_1.hasDirTraversal)(decodeURI(ctx.path)))
65
65
  return ctx.status = const_1.HTTP_FOOL;
66
66
  if (!ctx.state.skipFilters && (0, block_1.applyBlock)(ctx.socket, ctx.ip))
67
67
  return;
@@ -74,7 +74,7 @@ const someSecurity = (ctx, next) => {
74
74
  if (ctx.get('cf-ray'))
75
75
  exports.cloudflareDetected = new Date();
76
76
  }
77
- catch (_a) {
77
+ catch {
78
78
  return ctx.status = const_1.HTTP_FOOL;
79
79
  }
80
80
  if (!ctx.secure && forceHttps.get() && (0, listen_1.getHttpsWorkingPort)() && !(0, misc_1.isLocalHost)(ctx)) {
@@ -89,20 +89,19 @@ const someSecurity = (ctx, next) => {
89
89
  exports.someSecurity = someSecurity;
90
90
  // limited to http proxies
91
91
  function getProxyDetected() {
92
- if ((proxyDetected === null || proxyDetected === void 0 ? void 0 : proxyDetected.state.whenProxyDetected) < Date.now() - misc_1.DAY) // detection is reset after a day
92
+ if (proxyDetected?.state.whenProxyDetected < Date.now() - misc_1.DAY) // detection is reset after a day
93
93
  proxyDetected = undefined;
94
94
  return proxyDetected && { from: proxyDetected.socket.remoteAddress, for: proxyDetected.get('X-Forwarded-For') };
95
95
  }
96
96
  const prepareState = async (ctx, next) => {
97
- var _a, _b;
98
- if ((_a = ctx.session) === null || _a === void 0 ? void 0 : _a.username) {
97
+ if (ctx.session?.username) {
99
98
  if (ctx.session.ts < auth_1.invalidateSessionBefore.get(ctx.session.username))
100
99
  delete ctx.session.username;
101
100
  ctx.session.maxAge = exports.sessionDuration.compiled();
102
101
  }
103
102
  // calculate these once and for all
104
103
  ctx.state.connection = (0, connections_1.socket2connection)(ctx.socket);
105
- const a = ctx.state.account = await urlLogin() || await getHttpAccount() || (0, perm_1.getAccount)((_b = ctx.session) === null || _b === void 0 ? void 0 : _b.username, false);
104
+ const a = ctx.state.account = await urlLogin() || await getHttpAccount() || (0, perm_1.getAccount)(ctx.session?.username, false);
106
105
  if (a && (!(0, perm_1.accountCanLogin)(a) || failAllowNet(ctx, a))) // enforce allow_net also after login
107
106
  ctx.state.account = undefined;
108
107
  ctx.state.revProxyPath = ctx.get('x-forwarded-prefix');
@@ -117,25 +116,23 @@ const prepareState = async (ctx, next) => {
117
116
  return u && (0, auth_1.clearTextLogin)(ctx, u, p, 'url');
118
117
  }
119
118
  function getHttpAccount() {
120
- var _a, _b;
121
- const b64 = allowAuthorizationHeader.get() && ((_a = ctx.get('authorization')) === null || _a === void 0 ? void 0 : _a.split(' ')[1]);
119
+ const b64 = allowAuthorizationHeader.get() && ctx.get('authorization')?.split(' ')[1];
122
120
  if (!b64)
123
121
  return;
124
122
  try {
125
123
  const [u, p] = atob(b64).split(':');
126
- if (!u || u === ((_b = ctx.session) === null || _b === void 0 ? void 0 : _b.username))
124
+ if (!u || u === ctx.session?.username)
127
125
  return; // providing credentials, but not needed
128
126
  return (0, auth_1.clearTextLogin)(ctx, u, p || '', 'header');
129
127
  }
130
- catch (_c) { }
128
+ catch { }
131
129
  }
132
130
  };
133
131
  exports.prepareState = prepareState;
134
132
  function failAllowNet(ctx, a) {
135
- var _a, _b;
136
- const cached = (_a = ctx.session) === null || _a === void 0 ? void 0 : _a.allowNet; // won't reflect changes until session is terminated
137
- const mask = cached !== null && cached !== void 0 ? cached : (0, perm_1.getFromAccount)(a || '', a => a.allow_net);
138
- if (!cached && mask && ((_b = ctx.session) === null || _b === void 0 ? void 0 : _b.username))
133
+ const cached = ctx.session?.allowNet; // won't reflect changes until session is terminated
134
+ const mask = cached ?? (0, perm_1.getFromAccount)(a || '', a => a.allow_net);
135
+ if (!cached && mask && ctx.session?.username)
139
136
  ctx.session.allowNet = mask; // must be deleted on logout by setLoggedIn
140
137
  const ret = mask && !(0, misc_1.netMatches)(ctx.ip, mask, true);
141
138
  if (ret)