hfs 0.55.4 → 0.56.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 (50) hide show
  1. package/admin/assets/index-Cps81be4.js +819 -0
  2. package/admin/assets/index-D3kXFfFt.css +1 -0
  3. package/admin/assets/sha512-CjwnYWMV.js +8 -0
  4. package/admin/index.html +2 -2
  5. package/frontend/assets/index-legacy-B70uZPX1.js +50 -0
  6. package/frontend/assets/polyfills-legacy-Ca1TOBqp.js +1 -0
  7. package/frontend/assets/sha512-legacy-gnUzOTiL.js +9 -0
  8. package/frontend/index.html +2 -2
  9. package/package.json +1 -1
  10. package/src/SendList.js +1 -1
  11. package/src/adminApis.js +1 -1
  12. package/src/api.auth.js +2 -0
  13. package/src/api.get_file_list.js +2 -4
  14. package/src/api.vfs.js +11 -16
  15. package/src/auth.js +1 -0
  16. package/src/basicWeb.js +1 -1
  17. package/src/commands.js +3 -6
  18. package/src/config.js +32 -11
  19. package/src/const.js +2 -2
  20. package/src/cross-const.js +2 -1
  21. package/src/cross.js +1 -1
  22. package/src/debounceAsync.js +3 -2
  23. package/src/dirStream.js +1 -1
  24. package/src/frontEndApis.js +23 -0
  25. package/src/icons.js +35 -0
  26. package/src/index.js +3 -0
  27. package/src/langs/hfs-lang-hu.json +10 -6
  28. package/src/langs/hfs-lang-it.json +2 -1
  29. package/src/log.js +1 -1
  30. package/src/middlewares.js +11 -2
  31. package/src/misc.js +7 -1
  32. package/src/nat.js +1 -1
  33. package/src/outboundProxy.js +43 -0
  34. package/src/plugins.js +25 -14
  35. package/src/serveGuiAndSharedFiles.js +15 -0
  36. package/src/serveGuiFiles.js +5 -0
  37. package/src/update.js +1 -1
  38. package/src/upload.js +12 -1
  39. package/src/util-files.js +3 -1
  40. package/src/util-http.js +14 -7
  41. package/src/util-os.js +5 -1
  42. package/src/vfs.js +28 -9
  43. package/src/watchLoad.js +3 -2
  44. package/src/zip.js +4 -2
  45. package/admin/assets/index-C840XOsq.js +0 -809
  46. package/admin/assets/index-CrbMquLL.css +0 -1
  47. package/admin/assets/sha512-CgS2_xLX.js +0 -8
  48. package/frontend/assets/index-legacy-ckp4YJR7.js +0 -60
  49. package/frontend/assets/polyfills-legacy-DMrMt_pQ.js +0 -4
  50. package/frontend/assets/sha512-legacy-FLDdvu6g.js +0 -9
package/src/log.js CHANGED
@@ -56,7 +56,7 @@ class Logger {
56
56
  return this.stream = undefined;
57
57
  try {
58
58
  const stats = await (0, promises_1.stat)(path);
59
- this.last = stats.mtime || stats.ctime;
59
+ this.last = stats.mtime || stats.birthtime;
60
60
  }
61
61
  catch (_b) {
62
62
  if (await (0, util_files_1.prepareFolder)(path) === false)
@@ -4,7 +4,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  return (mod && mod.__esModule) ? mod : { "default": mod };
5
5
  };
6
6
  Object.defineProperty(exports, "__esModule", { value: true });
7
- exports.sessionMiddleware = exports.paramsDecoder = exports.prepareState = exports.getProxyDetected = exports.someSecurity = exports.cloudflareDetected = exports.headRequests = exports.gzipper = exports.sessionDuration = void 0;
7
+ exports.sessionMiddleware = exports.paramsDecoder = exports.failAllowNet = exports.prepareState = exports.getProxyDetected = exports.someSecurity = exports.cloudflareDetected = exports.headRequests = exports.gzipper = exports.sessionDuration = void 0;
8
8
  const koa_compress_1 = __importDefault(require("koa-compress"));
9
9
  const const_1 = require("./const");
10
10
  const misc_1 = require("./misc");
@@ -106,7 +106,7 @@ const prepareState = async (ctx, next) => {
106
106
  // calculate these once and for all
107
107
  ctx.state.connection = (0, connections_1.socket2connection)(ctx.socket);
108
108
  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);
109
- if (a && !(0, perm_1.accountCanLogin)(a))
109
+ if (a && (!(0, perm_1.accountCanLogin)(a) || failAllowNet(ctx, a))) // enforce allow_net also after login
110
110
  ctx.state.account = undefined;
111
111
  ctx.state.revProxyPath = ctx.get('x-forwarded-prefix');
112
112
  (0, connections_1.updateConnectionForCtx)(ctx);
@@ -147,6 +147,15 @@ const prepareState = async (ctx, next) => {
147
147
  }
148
148
  };
149
149
  exports.prepareState = prepareState;
150
+ function failAllowNet(ctx, a) {
151
+ var _a, _b;
152
+ const cached = (_a = ctx.session) === null || _a === void 0 ? void 0 : _a.allowNet; // won't reflect changes until session is terminated
153
+ const mask = cached !== null && cached !== void 0 ? cached : (0, perm_1.getFromAccount)(a || '', a => a.allow_net);
154
+ if (!cached && mask && ((_b = ctx.session) === null || _b === void 0 ? void 0 : _b.username))
155
+ ctx.session.allowNet = mask; // must be deleted on logout by setLoggedIn
156
+ return mask && !(0, misc_1.netMatches)(ctx.ip, mask, true);
157
+ }
158
+ exports.failAllowNet = failAllowNet;
150
159
  const paramsDecoder = async (ctx, next) => {
151
160
  ctx.state.params = ctx.method === 'POST' && ctx.originalUrl.startsWith(const_1.API_URI)
152
161
  && ((0, misc_1.tryJson)(await (0, misc_1.stream2string)(ctx.req)) || {});
package/src/misc.js CHANGED
@@ -18,7 +18,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
18
18
  return (mod && mod.__esModule) ? mod : { "default": mod };
19
19
  };
20
20
  Object.defineProperty(exports, "__esModule", { value: true });
21
- exports.deleteNode = exports.createStreamLimiter = exports.apiAssertTypes = exports.AsapStream = exports.asyncGeneratorToReadable = exports.same = exports.makeNetMatcher = exports.isLocalHost = exports.pattern2filter = void 0;
21
+ exports.deleteNode = exports.createStreamLimiter = exports.apiAssertTypes = exports.AsapStream = exports.asyncGeneratorToReadable = exports.same = exports.makeNetMatcher = exports.netMatches = exports.isLocalHost = exports.pattern2filter = void 0;
22
22
  const path_1 = require("path");
23
23
  const assert_1 = __importDefault(require("assert"));
24
24
  __exportStar(require("./util-http"), exports);
@@ -36,6 +36,7 @@ const vfs_1 = require("./vfs");
36
36
  const events_1 = __importDefault(require("./events"));
37
37
  const promises_1 = require("fs/promises");
38
38
  const comments_1 = require("./comments");
39
+ const lodash_1 = __importDefault(require("lodash"));
39
40
  function pattern2filter(pattern) {
40
41
  const matcher = (0, cross_1.makeMatcher)(pattern.includes('*') ? pattern // if you specify *, we'll respect its position
41
42
  : pattern.split('|').map(x => `*${x}*`).join('|'));
@@ -47,6 +48,11 @@ function isLocalHost(c) {
47
48
  return ip && (0, cross_1.isIpLocalHost)(ip);
48
49
  }
49
50
  exports.isLocalHost = isLocalHost;
51
+ // this will memory-leak over mask, so be careful with what you use this
52
+ function netMatches(ip, mask, emptyMaskReturns = false) {
53
+ return lodash_1.default.memoize(makeNetMatcher, (a, b) => `${a}\t${b ? 1 : 0}`)(mask, emptyMaskReturns)(ip); // cache the matcher
54
+ }
55
+ exports.netMatches = netMatches;
50
56
  function makeNetMatcher(mask, emptyMaskReturns = false) {
51
57
  var _a;
52
58
  if (!mask)
package/src/nat.js CHANGED
@@ -92,7 +92,7 @@ exports.getNatInfo = (0, debounceAsync_1.debounceAsync)(async () => {
92
92
  externalPort,
93
93
  proto: ((_c = status === null || status === void 0 ? void 0 : status.https) === null || _c === void 0 ? void 0 : _c.listening) ? 'https' : ((_d = status === null || status === void 0 ? void 0 : status.http) === null || _d === void 0 ? void 0 : _d.listening) ? 'http' : '',
94
94
  };
95
- });
95
+ }, { reuseRunning: true });
96
96
  (0, exports.getNatInfo)();
97
97
  function findGateway() {
98
98
  return new Promise((resolve, reject) => (0, child_process_1.exec)(const_1.IS_WINDOWS || const_1.IS_MAC ? 'netstat -rn' : 'route -n', (err, out) => {
@@ -0,0 +1,43 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const config_1 = require("./config");
7
+ const node_url_1 = require("node:url");
8
+ const util_http_1 = require("./util-http");
9
+ const util_os_1 = require("./util-os");
10
+ const events_1 = __importDefault(require("./events"));
11
+ const const_1 = require("./const");
12
+ const cross_1 = require("./cross");
13
+ // don't move this in util-http, where it would mostly belong, as a require to config.ts would prevent tests using util-http
14
+ const outboundProxy = (0, config_1.defineConfig)('outbound_proxy', '', v => {
15
+ try {
16
+ (0, node_url_1.parse)(v);
17
+ util_http_1.httpStream.defaultProxy = v;
18
+ }
19
+ catch (_a) {
20
+ console.warn("invalid URL", v);
21
+ return '';
22
+ }
23
+ });
24
+ events_1.default.once('configReady', async (startedWithoutConfig) => {
25
+ var _a, _b, _c, _d;
26
+ if (!const_1.IS_WINDOWS || !startedWithoutConfig)
27
+ return;
28
+ // try to read Windows system setting for proxy
29
+ const out = await (0, util_os_1.reg)('query', 'HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings');
30
+ if (!((_a = /ProxyEnable.+(\d)/.exec(out)) === null || _a === void 0 ? void 0 : _a[1]))
31
+ return;
32
+ const read = (_b = /ProxyServer.+?([\d:.]+)/.exec(out)) === null || _b === void 0 ? void 0 : _b[1];
33
+ if (!read)
34
+ return;
35
+ // it can be like "IP:PORT" or "http=IP:PORT;https=IP:PORT;ftp=IP:PORT"
36
+ const url = (0, cross_1.prefix)('https://', (_c = /https=([\d:.]+)/.exec(out)) === null || _c === void 0 ? void 0 : _c[1]) // prefer https
37
+ || (0, cross_1.prefix)('http://', (_d = /http=([\d:.]+)/.exec(out)) === null || _d === void 0 ? void 0 : _d[1])
38
+ || !read.includes('=') && 'http://' + read; // simpler form
39
+ if (!url)
40
+ return;
41
+ outboundProxy.set(url);
42
+ console.log("detected proxy", read);
43
+ });
package/src/plugins.js CHANGED
@@ -52,6 +52,7 @@ const lang_1 = require("./lang");
52
52
  const i18n_1 = require("./i18n");
53
53
  const perm_1 = require("./perm");
54
54
  const auth_1 = require("./auth");
55
+ const icons_1 = require("./icons");
55
56
  exports.PATH = 'plugins';
56
57
  exports.DISABLING_SUFFIX = '-disabled';
57
58
  exports.STORAGE_FOLDER = 'storage';
@@ -110,9 +111,8 @@ function setPluginConfig(id, changes) {
110
111
  }
111
112
  exports.setPluginConfig = setPluginConfig;
112
113
  function getPluginInfo(id) {
113
- var _a;
114
- const running = (_a = plugins.get(id)) === null || _a === void 0 ? void 0 : _a.getData();
115
- return running && Object.assign(running, { id }) || availablePlugins[id];
114
+ const running = plugins.get(id);
115
+ return running && { ...running.getData(), ...running } || availablePlugins[id];
116
116
  }
117
117
  exports.getPluginInfo = getPluginInfo;
118
118
  function findPluginByRepo(repo) {
@@ -139,6 +139,7 @@ async function initPlugin(pl, morePassedToInit) {
139
139
  getConnections: connections_1.getConnections,
140
140
  events: events_1.default,
141
141
  log: console.log,
142
+ setError(msg) { setError((morePassedToInit === null || morePassedToInit === void 0 ? void 0 : morePassedToInit.id) || 'server_code', msg); },
142
143
  getHfsConfig: config_1.getConfig,
143
144
  customApiCall,
144
145
  notifyClient: frontEndApis_1.notifyClient,
@@ -148,7 +149,9 @@ async function initPlugin(pl, morePassedToInit) {
148
149
  getCurrentUsername: auth_1.getCurrentUsername,
149
150
  ...morePassedToInit
150
151
  }));
151
- return Object.assign(pl, typeof res === 'function' ? { unload: res } : res);
152
+ Object.assign(pl, typeof res === 'function' ? { unload: res } : res);
153
+ events_1.default.emit('pluginInitialized', pl);
154
+ return pl;
152
155
  }
153
156
  const already = new Set();
154
157
  function warnOnce(msg) {
@@ -208,12 +211,12 @@ const pluginsMiddleware = async (ctx, next) => {
208
211
  catch (e) {
209
212
  printError(id, e);
210
213
  }
214
+ function printError(id, e) {
215
+ console.log(`error middleware plugin ${id}: ${(e === null || e === void 0 ? void 0 : e.message) || e}`);
216
+ console.debug(e);
217
+ }
211
218
  };
212
219
  exports.pluginsMiddleware = pluginsMiddleware;
213
- function printError(id, e) {
214
- console.log(`error middleware plugin ${id}: ${(e === null || e === void 0 ? void 0 : e.message) || e}`);
215
- console.debug(e);
216
- }
217
220
  events_1.default.once('app', () => Object.assign(index_1.app.context, {
218
221
  isStopped: false,
219
222
  stop() { return this.isStopped = true; }
@@ -280,7 +283,7 @@ class Plugin {
280
283
  return (_a = this.data) === null || _a === void 0 ? void 0 : _a.onDirEntry;
281
284
  }
282
285
  getData() {
283
- return { ...this.data };
286
+ return this.data;
284
287
  }
285
288
  async unload(reloading = false) {
286
289
  var _a, _b;
@@ -425,9 +428,8 @@ function watchPlugin(id, path) {
425
428
  };
426
429
  async function onUninstalled() {
427
430
  await stop();
428
- const was = getPluginInfo(id);
429
- if (!was)
430
- return;
431
+ if (!getPluginInfo(id))
432
+ return; // already missing
431
433
  delete availablePlugins[id];
432
434
  events_1.default.emit('pluginUninstalled', id);
433
435
  }
@@ -474,6 +476,7 @@ function watchPlugin(id, path) {
474
476
  await (0, promises_1.mkdir)(storageDir, { recursive: true });
475
477
  const dbs = [];
476
478
  await initPlugin(pluginData, {
479
+ id,
477
480
  srcDir: __dirname,
478
481
  storageDir,
479
482
  async openDb(filename, options) {
@@ -516,7 +519,9 @@ function watchPlugin(id, path) {
516
519
  const folder = (0, path_1.dirname)(module);
517
520
  const { sections, unwatch } = (0, customHtml_1.watchLoadCustomHtml)(folder);
518
521
  pluginData.getCustomHtml = () => Object.assign(Object.fromEntries(sections), (0, misc_1.callable)(pluginData.customHtml) || {});
522
+ const unwatchIcons = (0, icons_1.watchIconsFolder)(folder, v => plugin.icons = v);
519
523
  const plugin = new Plugin(id, folder, pluginData, async () => {
524
+ unwatchIcons();
520
525
  unwatch();
521
526
  await Promise.allSettled(dbs.map(x => x.close()));
522
527
  dbs.length = 0;
@@ -536,7 +541,6 @@ function watchPlugin(id, path) {
536
541
  const parsed = (_a = e.stack) === null || _a === void 0 ? void 0 : _a.split('\n\n'); // this form is used by syntax-errors inside the plugin, which is useful to show
537
542
  const where = (parsed === null || parsed === void 0 ? void 0 : parsed.length) > 1 ? `\n${parsed[0]}` : '';
538
543
  e = e.message + where || String(e);
539
- console.log(`plugin error: ${id}:`, e);
540
544
  setError(id, e);
541
545
  }
542
546
  finally {
@@ -552,12 +556,19 @@ function getError(id) {
552
556
  var _a;
553
557
  return (_a = getPluginInfo(id)) === null || _a === void 0 ? void 0 : _a.error;
554
558
  }
559
+ // returns true if there's an error, and it has changed
555
560
  function setError(id, error) {
556
561
  const info = getPluginInfo(id);
557
562
  if (!info)
558
563
  return;
564
+ if (info.error === error)
565
+ return;
559
566
  info.error = error;
560
- events_1.default.emit('pluginUpdated', { id, error });
567
+ events_1.default.emit('pluginUpdated', info);
568
+ if (!error)
569
+ return;
570
+ console.log(`plugin error: ${id}:`, error);
571
+ return true;
561
572
  }
562
573
  function deleteModule(id) {
563
574
  var _a;
@@ -21,11 +21,14 @@ const koa_mount_1 = __importDefault(require("koa-mount"));
21
21
  const listen_1 = require("./listen");
22
22
  const misc_1 = require("./misc");
23
23
  const basicWeb_1 = require("./basicWeb");
24
+ const icons_1 = require("./icons");
25
+ const plugins_1 = require("./plugins");
24
26
  const serveFrontendFiles = (0, serveGuiFiles_1.serveGuiFiles)(process.env.FRONTEND_PROXY, cross_const_1.FRONTEND_URI);
25
27
  const serveFrontendPrefixed = (0, koa_mount_1.default)(cross_const_1.FRONTEND_URI.slice(0, -1), serveFrontendFiles);
26
28
  const serveAdminFiles = (0, serveGuiFiles_1.serveGuiFiles)(process.env.ADMIN_PROXY, cross_const_1.ADMIN_URI);
27
29
  const serveAdminPrefixed = (0, koa_mount_1.default)(cross_const_1.ADMIN_URI.slice(0, -1), serveAdminFiles);
28
30
  const serveGuiAndSharedFiles = async (ctx, next) => {
31
+ var _a;
29
32
  const { path } = ctx;
30
33
  // dynamic import on frontend|admin (used for non-https login) while developing (vite4) is not producing a relative path
31
34
  if (const_1.DEV && path.startsWith('/node_modules/')) {
@@ -40,6 +43,18 @@ const serveGuiAndSharedFiles = async (ctx, next) => {
40
43
  if (path.startsWith(cross_const_1.ADMIN_URI))
41
44
  return (0, adminApis_1.allowAdmin)(ctx) ? serveAdminPrefixed(ctx, next)
42
45
  : (0, errorPages_1.sendErrorPage)(ctx, cross_const_1.HTTP_FORBIDDEN);
46
+ if (path.startsWith(cross_const_1.ICONS_URI)) {
47
+ const a = path.substring(cross_const_1.ICONS_URI.length).split('/');
48
+ const iconName = a.at(-1);
49
+ if (!iconName)
50
+ return;
51
+ const plugin = a.length > 1 && (0, plugins_1.getPluginInfo)(a[0]); // an extra level in the path indicates a plugin
52
+ const file = plugin ? (_a = plugin.icons) === null || _a === void 0 ? void 0 : _a[iconName] : icons_1.customizedIcons === null || icons_1.customizedIcons === void 0 ? void 0 : icons_1.customizedIcons[iconName];
53
+ if (!file)
54
+ return;
55
+ ctx.state.considerAsGui = true;
56
+ return (0, serveFile_1.serveFile)(ctx, (0, path_1.join)((plugin === null || plugin === void 0 ? void 0 : plugin.folder) || '', icons_1.ICONS_FOLDER, file), const_1.MIME_AUTO);
57
+ }
43
58
  if (ctx.method === 'PUT') { // curl -T file url/
44
59
  const decPath = decodeURIComponent(path);
45
60
  let rest = (0, path_1.basename)(decPath);
@@ -19,6 +19,7 @@ const lodash_1 = __importDefault(require("lodash"));
19
19
  const config_1 = require("./config");
20
20
  const lang_1 = require("./lang");
21
21
  const upload_1 = require("./upload");
22
+ const icons_1 = require("./icons");
22
23
  const size1024 = (0, config_1.defineConfig)(misc_1.CFG.size_1024, false, x => misc_1.formatBytes.k = x ? 1024 : 1000); // we both configure formatBytes, and also provide a compiled version (number instead of boolean)
23
24
  const splitUploads = (0, config_1.defineConfig)(misc_1.CFG.split_uploads, 0);
24
25
  exports.logGui = (0, config_1.defineConfig)(misc_1.CFG.log_gui, false);
@@ -107,6 +108,7 @@ async function treatIndex(ctx, filesUri, body) {
107
108
  forceTheme: (0, plugins_1.mapPlugins)(p => lodash_1.default.isString(p.isTheme) ? p.isTheme : undefined).find(Boolean),
108
109
  customHtml: lodash_1.default.omit((0, customHtml_1.getAllSections)(), ['top', 'bottom', 'htmlHead', 'style']),
109
110
  ...(0, misc_1.newObj)(misc_1.FRONTEND_OPTIONS, (v, k) => (0, config_1.getConfig)(k)),
111
+ icons: Object.assign({}, ...(0, plugins_1.mapPlugins)(p => iconsToObj(p.icons, p.id + '/')), iconsToObj(icons_1.customizedIcons)),
110
112
  lang
111
113
  }, null, 4).replace(/<(\/script)/g, '<"+"$1') /*avoid breaking our script container*/}
112
114
  document.documentElement.setAttribute('ver', HFS.VERSION.split('-')[0])
@@ -116,6 +118,9 @@ async function treatIndex(ctx, filesUri, body) {
116
118
  <link rel="shortcut icon" href="/favicon.ico?${timestamp}" />
117
119
  ${(0, customHtml_1.getSection)('htmlHead')}`}
118
120
  `;
121
+ function iconsToObj(icons, pre = '') {
122
+ return icons && (0, misc_1.objSameKeys)(icons, (v, k) => const_1.ICONS_URI + pre + k);
123
+ }
119
124
  if (isBody && isOpen)
120
125
  return `${all}
121
126
  ${isFrontend && (0, customHtml_1.getSection)('top')}
package/src/update.js CHANGED
@@ -93,7 +93,7 @@ function localUpdateAvailable() {
93
93
  }
94
94
  exports.localUpdateAvailable = localUpdateAvailable;
95
95
  async function updateSupported() {
96
- return argv_1.argv.forceupdate || const_1.IS_BINARY && !await util_os_1.RUNNING_AS_SERVICE;
96
+ return process.env.DISABLE_UPDATE ? false : (argv_1.argv.forceupdate || const_1.IS_BINARY && !await util_os_1.RUNNING_AS_SERVICE);
97
97
  }
98
98
  exports.updateSupported = updateSupported;
99
99
  async function update(tagOrUrl = '') {
package/src/upload.js CHANGED
@@ -20,10 +20,21 @@ const lodash_1 = __importDefault(require("lodash"));
20
20
  const events_1 = __importDefault(require("./events"));
21
21
  const promises_1 = require("fs/promises");
22
22
  const expiringCache_1 = require("./expiringCache");
23
+ const first_1 = require("./first");
23
24
  exports.deleteUnfinishedUploadsAfter = (0, config_1.defineConfig)('delete_unfinished_uploads_after', 86400);
24
25
  exports.minAvailableMb = (0, config_1.defineConfig)('min_available_mb', 100);
25
26
  exports.dontOverwriteUploading = (0, config_1.defineConfig)('dont_overwrite_uploading', true);
26
27
  const waitingToBeDeleted = {};
28
+ (0, first_1.onProcessExit)(() => {
29
+ if (!Object.keys(waitingToBeDeleted).length)
30
+ return;
31
+ console.log("removing unfinished uploads");
32
+ for (const path in waitingToBeDeleted)
33
+ try {
34
+ fs_1.default.rmSync(path, { force: true });
35
+ }
36
+ catch (_a) { }
37
+ });
27
38
  const ATTR_UPLOADER = 'uploader';
28
39
  function getUploadMeta(path) {
29
40
  return (0, misc_1.loadFileAttr)(path, ATTR_UPLOADER);
@@ -148,7 +159,7 @@ function uploadWriter(base, baseUri, path, ctx) {
148
159
  writeStream.once('close', async () => {
149
160
  try {
150
161
  await new Promise(res => fileStream.close(res)); // this only seem to be necessary on Windows
151
- if (ctx.req.aborted) {
162
+ if (ctx.isAborted()) {
152
163
  if (resumableTempName && !resumableLost && !resuming) // we don't want to be left with 2 temp files
153
164
  return (0, promises_1.rm)(tempName);
154
165
  const sec = exports.deleteUnfinishedUploadsAfter.get();
package/src/util-files.js CHANGED
@@ -31,7 +31,7 @@ async function readFileBusy(path) {
31
31
  });
32
32
  }
33
33
  exports.readFileBusy = readFileBusy;
34
- function watchDir(dir, cb) {
34
+ function watchDir(dir, cb, atStart = false) {
35
35
  let watcher;
36
36
  let paused = false;
37
37
  try {
@@ -56,6 +56,8 @@ function watchDir(dir, cb) {
56
56
  console.debug(String(e));
57
57
  }
58
58
  }
59
+ if (atStart)
60
+ controlledCb();
59
61
  return {
60
62
  working() { return Boolean(watcher); },
61
63
  stop() { watcher === null || watcher === void 0 ? void 0 : watcher.close(); },
package/src/util-http.js CHANGED
@@ -5,6 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  };
6
6
  Object.defineProperty(exports, "__esModule", { value: true });
7
7
  exports.httpStream = exports.httpWithBody = exports.httpString = exports.stream2string = void 0;
8
+ const node_url_1 = require("node:url");
8
9
  const node_https_1 = __importDefault(require("node:https"));
9
10
  const node_http_1 = __importDefault(require("node:http"));
10
11
  const node_stream_1 = require("node:stream");
@@ -23,25 +24,31 @@ async function httpWithBody(url, options) {
23
24
  });
24
25
  }
25
26
  exports.httpWithBody = httpWithBody;
26
- function httpStream(url, { body, jar, noRedirect, httpThrow, ...options } = {}) {
27
+ function httpStream(url, { body, jar, noRedirect, httpThrow, proxy, ...options } = {}) {
27
28
  return new Promise((resolve, reject) => {
28
- var _a, _b, _c;
29
- var _d, _e;
29
+ var _a, _b, _c, _d;
30
+ var _e, _f, _g;
31
+ proxy !== null && proxy !== void 0 ? proxy : (proxy = httpStream.defaultProxy);
30
32
  (_a = options.headers) !== null && _a !== void 0 ? _a : (options.headers = {});
31
33
  if (body) {
32
34
  options.method || (options.method = 'POST');
33
35
  if (lodash_1.default.isPlainObject(body)) {
34
- (_b = (_d = options.headers)['content-type']) !== null && _b !== void 0 ? _b : (_d['content-type'] = 'application/json');
36
+ (_b = (_e = options.headers)['content-type']) !== null && _b !== void 0 ? _b : (_e['content-type'] = 'application/json');
35
37
  body = JSON.stringify(body);
36
38
  }
37
39
  if (!(body instanceof node_stream_1.Readable))
38
- (_c = (_e = options.headers)['content-length']) !== null && _c !== void 0 ? _c : (_e['content-length'] = Buffer.byteLength(body));
40
+ (_c = (_f = options.headers)['content-length']) !== null && _c !== void 0 ? _c : (_f['content-length'] = Buffer.byteLength(body));
39
41
  }
40
42
  if (jar)
41
43
  options.headers.cookie = lodash_1.default.map(jar, (v, k) => `${k}=${v}; `).join('')
42
44
  + (options.headers.cookie || ''); // preserve parameter
43
- const proto = url.startsWith('https:') ? node_https_1.default : node_http_1.default;
44
- const req = proto.request(url, options, res => {
45
+ Object.assign(options, lodash_1.default.pick((0, node_url_1.parse)(proxy || url), ['hostname', 'port', 'path', 'protocol']));
46
+ if (proxy) {
47
+ options.path = url;
48
+ (_d = (_g = options.headers).host) !== null && _d !== void 0 ? _d : (_g.host = (0, node_url_1.parse)(url).host || undefined);
49
+ }
50
+ const proto = options.protocol === 'https:' ? node_https_1.default : node_http_1.default;
51
+ const req = proto.request(options, res => {
45
52
  var _a;
46
53
  var _b;
47
54
  console.debug("http responded", res.statusCode, "to", url);
package/src/util-os.js CHANGED
@@ -3,7 +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
- exports.RUNNING_AS_SERVICE = exports.runCmd = exports.getDrives = exports.getDiskSpaces = exports.getDiskSpace = exports.cmdEscape = exports.bashEscape = exports.getDiskSpaceSync = void 0;
6
+ exports.reg = exports.RUNNING_AS_SERVICE = exports.runCmd = exports.getDrives = exports.getDiskSpaces = exports.getDiskSpace = exports.cmdEscape = exports.bashEscape = exports.getDiskSpaceSync = void 0;
7
7
  const path_1 = require("path");
8
8
  const fs_1 = require("fs");
9
9
  const child_process_1 = require("child_process");
@@ -93,3 +93,7 @@ exports.RUNNING_AS_SERVICE = const_1.IS_WINDOWS && getWindowsServicePids().then(
93
93
  console.log("couldn't determine if we are running as a service");
94
94
  console.debug(e);
95
95
  });
96
+ function reg(...pars) {
97
+ return (0, util_1.promisify)(child_process_1.execFile)('reg', pars).then(x => x.stdout);
98
+ }
99
+ exports.reg = reg;
package/src/vfs.js CHANGED
@@ -4,7 +4,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  return (mod && mod.__esModule) ? mod : { "default": mod };
5
5
  };
6
6
  Object.defineProperty(exports, "__esModule", { value: true });
7
- exports.parentMaskApplier = exports.masksCouldGivePermission = exports.walkNode = exports.statusCodeForMissingPerm = exports.hasPermission = exports.nodeIsLink = exports.hasDefaultFile = exports.nodeIsDirectory = exports.getNodeName = exports.saveVfs = exports.vfs = exports.getNodeByName = exports.urlToNode = exports.applyParentToChild = exports.isSameFilenameAs = exports.permsFromParent = void 0;
7
+ exports.parentMaskApplier = exports.masksCouldGivePermission = exports.walkNode = exports.statusCodeForMissingPerm = exports.hasPermission = exports.nodeIsLink = exports.hasDefaultFile = exports.nodeIsDirectory = exports.getNodeName = exports.saveVfs = exports.vfs = exports.getNodeByName = exports.nodeStats = exports.urlToNode = exports.applyParentToChild = exports.isSameFilenameAs = exports.permsFromParent = void 0;
8
8
  const promises_1 = __importDefault(require("fs/promises"));
9
9
  const path_1 = require("path");
10
10
  const misc_1 = require("./misc");
@@ -112,6 +112,7 @@ async function nodeStats(ret) {
112
112
  (0, misc_1.setHidden)(ret, { stats });
113
113
  return stats;
114
114
  }
115
+ exports.nodeStats = nodeStats;
115
116
  async function isHiddenFile(path) {
116
117
  return const_1.IS_WINDOWS ? new Promise(res => fswin_1.default.getAttributes(path, x => res(x === null || x === void 0 ? void 0 : x.IS_HIDDEN)))
117
118
  : path[0] === '.';
@@ -144,6 +145,14 @@ async function getNodeByName(name, parent) {
144
145
  exports.getNodeByName = getNodeByName;
145
146
  exports.vfs = {};
146
147
  (0, config_1.defineConfig)('vfs', {}).sub(data => exports.vfs = (function recur(node) {
148
+ const { masks } = node;
149
+ lodash_1.default.each(masks, (v, mask) => {
150
+ if (v.maskOnly) {
151
+ masks[`${mask}|${v.maskOnly}|`] = v = lodash_1.default.omit(v, 'maskOnly');
152
+ delete masks[mask];
153
+ }
154
+ recur(v);
155
+ });
147
156
  if (node.children)
148
157
  for (const c of node.children)
149
158
  recur(c);
@@ -273,9 +282,9 @@ async function* walkNode(parent, { ctx, depth = Infinity, prefixPath = '', requi
273
282
  try {
274
283
  let lastDir = prefixPath.slice(0, -1) || '.';
275
284
  parentsCache.set(lastDir, parent);
276
- for await (const entry of (0, misc_1.dirStream)(source, { depth, onlyFolders, onlyFiles, hidden: showHiddenFiles.get() })) {
277
- if (ctx === null || ctx === void 0 ? void 0 : ctx.req.aborted)
278
- return;
285
+ for await (const entry of (0, misc_1.dirStream)(source, { depth, onlyFolders, hidden: showHiddenFiles.get() })) {
286
+ if (ctx === null || ctx === void 0 ? void 0 : ctx.isAborted())
287
+ break;
279
288
  const { path } = entry;
280
289
  const isFolder = entry.isDirectory();
281
290
  const name = prefixPath + (((_a = parent.rename) === null || _a === void 0 ? void 0 : _a[path]) || path);
@@ -294,7 +303,7 @@ async function* walkNode(parent, { ctx, depth = Infinity, prefixPath = '', requi
294
303
  };
295
304
  if (isFolder) // store it even if we can't see it (masks), as its children can be produced by dirStream
296
305
  parentsCache.set(name, item);
297
- if (await canSee(item))
306
+ if (!(onlyFiles && isFolder) && await canSee(item))
298
307
  yield item;
299
308
  (_b = entry.closingBranch) === null || _b === void 0 ? void 0 : _b.then(p => parentsCache.delete(p || '.'));
300
309
  }
@@ -320,11 +329,21 @@ function masksCouldGivePermission(masks, perm) {
320
329
  }
321
330
  exports.masksCouldGivePermission = masksCouldGivePermission;
322
331
  function parentMaskApplier(parent) {
323
- const matchers = (0, misc_1.onlyTruthy)(lodash_1.default.map(parent.masks, (v, k) => {
332
+ const matchers = (0, misc_1.onlyTruthy)(lodash_1.default.map(parent.masks, (mods, k) => {
333
+ if (!mods)
334
+ return;
335
+ const mustBeFolder = (() => {
336
+ if (k.at(-1) !== '|')
337
+ return; // parse special flag syntax as suffix |FLAG| inside the key. This allows specifying different flags with the same mask using separate keys. To avoid syntax conflicts with the rest of the file-mask, we look for an ending pipe, as it has no practical use. Ending-pipe was preferred over starting-pipe to leave the rest of the logic (inheritMasks) untouched.
338
+ const i = k.lastIndexOf('|', k.length - 2);
339
+ if (i < 0)
340
+ return;
341
+ const type = k.slice(i + 1, -1);
342
+ k = k.slice(0, i); // remove
343
+ return type === 'folders';
344
+ })();
324
345
  k = k.startsWith('**/') ? k.slice(3) : !k.includes('/') ? k : ''; // ** globstar matches also zero subfolders, so this mask must be applied here too
325
- const { maskOnly, ...mods } = v || {};
326
- // k is stored into the object for debugging purposes
327
- return k && { k, mods, matcher: (0, misc_1.makeMatcher)(k), mustBeFolder: maskOnly && (maskOnly === 'folders') };
346
+ return k && { mods, matcher: (0, misc_1.makeMatcher)(k), mustBeFolder };
328
347
  }));
329
348
  return async (item, virtualBasename = getNodeName(item)) => {
330
349
  let isFolder = undefined;
package/src/watchLoad.js CHANGED
@@ -8,16 +8,17 @@ exports.watchLoad = void 0;
8
8
  const fs_1 = require("fs");
9
9
  const promises_1 = __importDefault(require("fs/promises"));
10
10
  const misc_1 = require("./misc");
11
+ const debounceAsync_1 = require("./debounceAsync");
11
12
  const events_1 = require("./events");
12
13
  function watchLoad(path, parser, { failedOnFirstAttempt, immediateFirst } = {}) {
13
14
  let doing = false;
14
15
  let watcher;
15
- const debounced = (0, misc_1.debounceAsync)(load, { wait: 500, maxWait: 1000 });
16
+ const debounced = (0, debounceAsync_1.debounceAsync)(load, { wait: 500, maxWait: 1000 });
16
17
  let retry;
17
18
  let last;
18
19
  const emitter = new events_1.BetterEventEmitter();
19
20
  install(true);
20
- const save = (0, misc_1.debounceAsync)(async (data, { reparse = false } = {}) => {
21
+ const save = (0, debounceAsync_1.debounceAsync)(async (data, { reparse = false } = {}) => {
21
22
  await promises_1.default.writeFile(path, data, 'utf8');
22
23
  last = data;
23
24
  if (reparse)
package/src/zip.js CHANGED
@@ -29,6 +29,8 @@ async function zipStreamFromFolder(node, ctx) {
29
29
  const walker = !list ? (0, vfs_1.walkNode)(node, { ctx, requiredPerm: 'can_archive' })
30
30
  : (async function* () {
31
31
  for await (const uri of list) {
32
+ if (ctx.isAborted())
33
+ return;
32
34
  const subNode = await (0, vfs_1.urlToNode)(uri, ctx, node);
33
35
  if (!subNode)
34
36
  continue;
@@ -51,7 +53,7 @@ async function zipStreamFromFolder(node, ctx) {
51
53
  return; // the fact you see it doesn't mean you can get it
52
54
  const { source } = el;
53
55
  const name = (0, vfs_1.getNodeName)(el);
54
- if (ctx.req.aborted || !filter(name))
56
+ if (!filter(name))
55
57
  return;
56
58
  try {
57
59
  if (el.isFolder)
@@ -64,7 +66,7 @@ async function zipStreamFromFolder(node, ctx) {
64
66
  return {
65
67
  path: name,
66
68
  size: st.size,
67
- ts: st.mtime || st.ctime,
69
+ ts: st.mtime || st.birthtime,
68
70
  mode: st.mode,
69
71
  sourcePath: source,
70
72
  getData: () => (0, fs_1.createReadStream)(source, { start: 0, end: Math.max(0, st.size - 1) })