hfs 0.26.9 → 0.29.0

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 (56) hide show
  1. package/README.md +17 -3
  2. package/admin/assets/index-cbb42a0e.js +415 -0
  3. package/admin/assets/index-f8049da8.css +1 -0
  4. package/{frontend/assets/sha512.6af42937.js → admin/assets/sha512-3273321f.js} +2 -2
  5. package/admin/index.html +2 -2
  6. package/frontend/assets/index-72e96bb2.js +85 -0
  7. package/frontend/assets/index-cbcc6ac5.css +1 -0
  8. package/{admin/assets/sha512.9dfe82e1.js → frontend/assets/sha512-2c2fa926.js} +2 -2
  9. package/frontend/index.html +3 -3
  10. package/package.json +8 -10
  11. package/plugins/vhosting/plugin.js +23 -20
  12. package/src/QuickZipStream.js +2 -25
  13. package/src/ThrottledStream.js +1 -1
  14. package/src/adminApis.js +6 -8
  15. package/src/api.accounts.js +10 -10
  16. package/src/api.auth.js +21 -17
  17. package/src/api.file_list.js +13 -6
  18. package/src/api.helpers.js +6 -6
  19. package/src/api.monitor.js +2 -0
  20. package/src/api.plugins.js +1 -0
  21. package/src/api.vfs.js +17 -19
  22. package/src/apiMiddleware.js +16 -9
  23. package/src/block.js +1 -0
  24. package/src/commands.js +1 -0
  25. package/src/config.js +3 -2
  26. package/src/connections.js +1 -1
  27. package/src/const.js +19 -7
  28. package/src/crypt.js +1 -1
  29. package/src/debounceAsync.js +1 -0
  30. package/src/events.js +1 -1
  31. package/src/frontEndApis.js +23 -2
  32. package/src/github.js +5 -1
  33. package/src/index.js +5 -3
  34. package/src/listen.js +6 -3
  35. package/src/log.js +6 -6
  36. package/src/middlewares.js +32 -26
  37. package/src/misc.js +27 -2
  38. package/src/perm.js +31 -29
  39. package/src/plugins.js +6 -8
  40. package/src/serveFile.js +15 -13
  41. package/src/serveGuiFiles.js +5 -28
  42. package/src/sse.js +3 -2
  43. package/src/throttler.js +1 -1
  44. package/src/update.js +3 -2
  45. package/src/upload.js +92 -0
  46. package/src/util-files.js +19 -13
  47. package/src/util-generators.js +1 -0
  48. package/src/util-http.js +3 -1
  49. package/src/util-os.js +41 -0
  50. package/src/vfs.js +44 -37
  51. package/src/watchLoad.js +1 -1
  52. package/src/zip.js +9 -6
  53. package/admin/assets/index.bb5198ec.js +0 -281
  54. package/admin/assets/index.dcc78777.css +0 -1
  55. package/frontend/assets/index.27a78796.js +0 -85
  56. package/frontend/assets/index.93366732.css +0 -1
package/src/perm.js CHANGED
@@ -1,10 +1,10 @@
1
1
  "use strict";
2
- // This file is part of HFS - Copyright 2021-2022, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt
2
+ // This file is part of HFS - Copyright 2021-2023, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt
3
3
  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.anyAccountCanLoginAdmin = exports.accountCanLoginAdmin = exports.accountCanLogin = exports.accountHasPassword = exports.getFromAccount = exports.delAccount = exports.setAccount = exports.addAccount = exports.renameAccount = exports.normalizeUsername = exports.updateAccount = exports.allowClearTextLogin = exports.saveSrpInfo = exports.getAccount = exports.getCurrentUsernameExpanded = exports.getCurrentUsername = exports.getAccounts = void 0;
7
+ exports.anyAccountCanLoginAdmin = exports.accountCanLoginAdmin = exports.accountCanLogin = exports.accountHasPassword = exports.getFromAccount = exports.delAccount = exports.setAccount = exports.addAccount = exports.renameAccount = exports.normalizeUsername = exports.accountsConfig = exports.updateAccount = exports.allowClearTextLogin = exports.saveSrpInfo = exports.getAccount = exports.getCurrentUsernameExpanded = exports.getCurrentUsername = void 0;
8
8
  const lodash_1 = __importDefault(require("lodash"));
9
9
  const crypt_1 = require("./crypt");
10
10
  const misc_1 = require("./misc");
@@ -12,10 +12,6 @@ const config_1 = require("./config");
12
12
  const tssrp6a_1 = require("tssrp6a");
13
13
  const events_1 = __importDefault(require("./events"));
14
14
  let accounts = {};
15
- function getAccounts() {
16
- return accounts;
17
- }
18
- exports.getAccounts = getAccounts;
19
15
  function getCurrentUsername(ctx) {
20
16
  var _a;
21
17
  return ((_a = ctx.state.account) === null || _a === void 0 ? void 0 : _a.username) || '';
@@ -63,27 +59,34 @@ async function updateAccount(account, changer) {
63
59
  console.log('please reset password for account', username);
64
60
  process.exit(1);
65
61
  }
66
- if (account.belongs)
67
- account.belongs = (0, misc_1.wantArray)(account.belongs).filter(b => b in accounts // at this stage the group record may still be null if specified later in the file
68
- || console.error(`account ${username} belongs to non-existing ${b}`));
62
+ if (account.belongs) {
63
+ account.belongs = (0, misc_1.wantArray)(account.belongs);
64
+ lodash_1.default.remove(account.belongs, b => {
65
+ if (b in accounts)
66
+ return;
67
+ console.error(`account ${username} belongs to non-existing ${b}`);
68
+ return true;
69
+ });
70
+ }
69
71
  if (was !== JSON.stringify(account))
70
72
  saveAccountsAsap();
71
73
  }
72
74
  exports.updateAccount = updateAccount;
73
- const saveAccountsAsap = config_1.saveConfigAsap;
74
- const accountsConfig = (0, config_1.defineConfig)('accounts', {});
75
- accountsConfig.sub(async (v) => {
76
- // we should validate content here
77
- accounts = v; // keep local reference
78
- await Promise.all(lodash_1.default.map(accounts, async (rec, k) => {
75
+ const saveAccountsAsap = () => { (0, config_1.saveConfigAsap)().then(); };
76
+ exports.accountsConfig = (0, config_1.defineConfig)('accounts', {});
77
+ exports.accountsConfig.sub(obj => {
78
+ // consider some validation here
79
+ lodash_1.default.each(accounts = obj, (rec, k) => {
79
80
  const norm = normalizeUsername(k);
80
- if (!rec) // an empty object in yaml is stored as null
81
- rec = accounts[norm] = { username: norm };
82
- else if ((0, misc_1.objRenameKey)(accounts, k, norm))
83
- saveAccountsAsap();
84
- (0, misc_1.setHidden)(rec, { username: norm });
85
- await updateAccount(rec); // work password fields
86
- }));
81
+ if ((rec === null || rec === void 0 ? void 0 : rec.username) !== norm) {
82
+ if (!rec) // an empty object in yaml is parsed as null
83
+ rec = obj[norm] = { username: norm };
84
+ else if ((0, misc_1.objRenameKey)(obj, k, norm))
85
+ saveAccountsAsap();
86
+ (0, misc_1.setHidden)(rec, { username: norm });
87
+ }
88
+ updateAccount(rec).then(); // work password fields
89
+ });
87
90
  });
88
91
  function normalizeUsername(username) {
89
92
  return username.toLocaleLowerCase();
@@ -120,8 +123,7 @@ function addAccount(username, props) {
120
123
  return;
121
124
  const filteredProps = lodash_1.default.pickBy(lodash_1.default.pick(props, assignableProps), Boolean);
122
125
  const copy = (0, misc_1.setHidden)(filteredProps, { username }); // have the field in the object but hidden so that stringification won't include it
123
- accountsConfig.set(accounts => Object.assign(accounts, { [username]: copy }));
124
- saveAccountsAsap().then();
126
+ exports.accountsConfig.set(accounts => Object.assign(accounts, { [username]: copy }));
125
127
  return copy;
126
128
  }
127
129
  exports.addAccount = addAccount;
@@ -136,15 +138,15 @@ function setAccount(username, changes) {
136
138
  Object.assign(acc, rest);
137
139
  if (changes.username)
138
140
  renameAccount(username, changes.username);
139
- saveAccountsAsap().then();
141
+ saveAccountsAsap();
140
142
  return acc;
141
143
  }
142
144
  exports.setAccount = setAccount;
143
145
  function delAccount(username) {
144
146
  if (!getAccount(username))
145
147
  return false;
146
- accountsConfig.set(accounts => Object.assign(accounts, { [normalizeUsername(username)]: undefined }));
147
- saveAccountsAsap().then();
148
+ exports.accountsConfig.set(accounts => Object.assign(accounts, { [normalizeUsername(username)]: undefined }));
149
+ saveAccountsAsap();
148
150
  return true;
149
151
  }
150
152
  exports.delAccount = delAccount;
@@ -172,10 +174,10 @@ function accountCanLogin(account) {
172
174
  }
173
175
  exports.accountCanLogin = accountCanLogin;
174
176
  function accountCanLoginAdmin(account) {
175
- return accountCanLogin(account) && getFromAccount(account, a => a.admin);
177
+ return accountCanLogin(account) && Boolean(getFromAccount(account, a => a.admin));
176
178
  }
177
179
  exports.accountCanLoginAdmin = accountCanLoginAdmin;
178
180
  function anyAccountCanLoginAdmin() {
179
- return Object.values(accounts).find(accountCanLoginAdmin);
181
+ return Boolean(lodash_1.default.find(exports.accountsConfig.get(), accountCanLoginAdmin));
180
182
  }
181
183
  exports.anyAccountCanLoginAdmin = anyAccountCanLoginAdmin;
package/src/plugins.js CHANGED
@@ -1,5 +1,5 @@
1
1
  "use strict";
2
- // This file is part of HFS - Copyright 2021-2022, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt
2
+ // This file is part of HFS - Copyright 2021-2023, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt
3
3
  var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
4
4
  if (k2 === undefined) k2 = k;
5
5
  var desc = Object.getOwnPropertyDescriptor(m, k);
@@ -92,9 +92,8 @@ function pluginsMiddleware() {
92
92
  var _a;
93
93
  const after = [];
94
94
  // run middleware plugins
95
- for (const id in plugins)
95
+ for (const [id, pl] of Object.entries(plugins))
96
96
  try {
97
- const pl = plugins[id];
98
97
  const res = await ((_a = pl.middleware) === null || _a === void 0 ? void 0 : _a.call(pl, ctx));
99
98
  if (res === true)
100
99
  ctx.pluginStopped = true;
@@ -225,7 +224,7 @@ async function rescan() {
225
224
  try {
226
225
  const alreadyRunning = plugins[id];
227
226
  console.log(alreadyRunning ? "reloading plugin" : "loading plugin", id);
228
- const { init, ...data } = await Promise.resolve().then(() => __importStar(require(module)));
227
+ const { init, ...data } = await import(module);
229
228
  delete data.default;
230
229
  deleteModule(require.resolve(module)); // avoid caching at next import
231
230
  calculateBadApi(data);
@@ -277,8 +276,7 @@ async function rescan() {
277
276
  }
278
277
  });
279
278
  }
280
- for (const id in foundDisabled) {
281
- const p = foundDisabled[id];
279
+ for (const [id, p] of Object.entries(foundDisabled)) {
282
280
  const a = availablePlugins[id];
283
281
  if ((0, misc_1.same)(a, p))
284
282
  continue;
@@ -293,9 +291,9 @@ async function rescan() {
293
291
  delete availablePlugins[id];
294
292
  events_1.default.emit('pluginUninstalled', id);
295
293
  }
296
- for (const id in plugins)
294
+ for (const [id, p] of Object.entries(plugins))
297
295
  if (!found.includes(id))
298
- await plugins[id].unload();
296
+ await p.unload();
299
297
  }
300
298
  exports.rescan = rescan;
301
299
  function deleteModule(id) {
package/src/serveFile.js CHANGED
@@ -1,5 +1,5 @@
1
1
  "use strict";
2
- // This file is part of HFS - Copyright 2021-2022, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt
2
+ // This file is part of HFS - Copyright 2021-2023, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt
3
3
  var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  return (mod && mod.__esModule) ? mod : { "default": mod };
5
5
  };
@@ -27,7 +27,7 @@ function serveFileNode(node) {
27
27
  const ref = (_a = /\/\/([^:/]+)/.exec(ctx.get('referer'))) === null || _a === void 0 ? void 0 : _a[1]; // extract host from url
28
28
  if (ref && ref !== host() // automatic accept if referer is basically the hosting domain
29
29
  && !(0, micromatch_1.isMatch)(ref, allowed))
30
- return ctx.status = const_1.FORBIDDEN;
30
+ return ctx.status = const_1.HTTP_FORBIDDEN;
31
31
  function host() {
32
32
  const s = ctx.get('host');
33
33
  return s[0] === '[' ? s.slice(1, s.indexOf(']')) : s === null || s === void 0 ? void 0 : s.split(':')[0];
@@ -50,26 +50,26 @@ function serveFile(source, mime, content) {
50
50
  if (mime)
51
51
  ctx.type = mime;
52
52
  if (ctx.method === 'OPTIONS') {
53
- ctx.status = const_1.NO_CONTENT;
53
+ ctx.status = const_1.HTTP_NO_CONTENT;
54
54
  ctx.set({ Allow: 'OPTIONS, GET, HEAD' });
55
55
  return;
56
56
  }
57
57
  if (ctx.method !== 'GET')
58
- return ctx.status = const_1.METHOD_NOT_ALLOWED;
58
+ return ctx.status = const_1.HTTP_METHOD_NOT_ALLOWED;
59
59
  try {
60
60
  const stats = await (0, util_1.promisify)(fs_1.stat)(source); // using fs's function instead of fs/promises, because only the former is supported by pkg
61
61
  ctx.set('Last-Modified', stats.mtime.toUTCString());
62
62
  ctx.fileSource = source;
63
- ctx.status = 200;
63
+ ctx.status = const_1.HTTP_OK;
64
64
  if (ctx.fresh)
65
- return ctx.status = 304;
65
+ return ctx.status = const_1.HTTP_NOT_MODIFIED;
66
66
  if (content !== undefined)
67
67
  return ctx.body = content;
68
68
  const range = getRange(ctx, stats.size);
69
69
  ctx.body = (0, fs_1.createReadStream)(source, range);
70
70
  }
71
71
  catch (_a) {
72
- return ctx.status = 404;
72
+ return ctx.status = const_1.HTTP_NOT_FOUND;
73
73
  }
74
74
  };
75
75
  }
@@ -81,23 +81,25 @@ function getRange(ctx, totalSize) {
81
81
  ctx.response.length = totalSize;
82
82
  return;
83
83
  }
84
- const ranges = range.split('=')[1];
85
- if (ranges.includes(','))
86
- return ctx.throw(400, 'multi-range not supported');
84
+ const [unit, ranges] = range.split('=');
85
+ if (unit !== 'bytes')
86
+ return ctx.throw(const_1.HTTP_BAD_REQUEST, 'bad range unit');
87
+ if (ranges === null || ranges === void 0 ? void 0 : ranges.includes(','))
88
+ return ctx.throw(const_1.HTTP_BAD_REQUEST, 'multi-range not supported');
87
89
  let bytes = ranges === null || ranges === void 0 ? void 0 : ranges.split('-');
88
90
  if (!(bytes === null || bytes === void 0 ? void 0 : bytes.length))
89
- return ctx.throw(400, 'bad range');
91
+ return ctx.throw(const_1.HTTP_BAD_REQUEST, 'bad range');
90
92
  const max = totalSize - 1;
91
93
  const start = bytes[0] ? Number(bytes[0]) : Math.max(0, totalSize - Number(bytes[1])); // a negative start is relative to the end
92
94
  const end = bytes[0] ? Number(bytes[1] || max) : max;
93
95
  // we don't support last-bytes without knowing max
94
96
  if (isNaN(end) && isNaN(max) || end > max || start > max) {
95
- ctx.status = 416;
97
+ ctx.status = const_1.HTTP_RANGE_NOT_SATISFIABLE;
96
98
  ctx.set('Content-Range', `bytes ${totalSize}`);
97
99
  ctx.body = 'Requested Range Not Satisfiable';
98
100
  return;
99
101
  }
100
- ctx.status = 206;
102
+ ctx.status = const_1.HTTP_PARTIAL_CONTENT;
101
103
  ctx.set('Content-Range', `bytes ${start}-${isNaN(end) ? '' : end}/${isNaN(totalSize) ? '*' : totalSize}`);
102
104
  ctx.response.length = end - start + 1;
103
105
  return { start, end };
@@ -1,28 +1,5 @@
1
1
  "use strict";
2
- // This file is part of HFS - Copyright 2021-2022, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt
3
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
4
- if (k2 === undefined) k2 = k;
5
- var desc = Object.getOwnPropertyDescriptor(m, k);
6
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
7
- desc = { enumerable: true, get: function() { return m[k]; } };
8
- }
9
- Object.defineProperty(o, k2, desc);
10
- }) : (function(o, m, k, k2) {
11
- if (k2 === undefined) k2 = k;
12
- o[k2] = m[k];
13
- }));
14
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
15
- Object.defineProperty(o, "default", { enumerable: true, value: v });
16
- }) : function(o, v) {
17
- o["default"] = v;
18
- });
19
- var __importStar = (this && this.__importStar) || function (mod) {
20
- if (mod && mod.__esModule) return mod;
21
- var result = {};
22
- if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
23
- __setModuleDefault(result, mod);
24
- return result;
25
- };
2
+ // This file is part of HFS - Copyright 2021-2023, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt
26
3
  var __importDefault = (this && this.__importDefault) || function (mod) {
27
4
  return (mod && mod.__esModule) ? mod : { "default": mod };
28
5
  };
@@ -43,12 +20,12 @@ function serveStatic(uri) {
43
20
  const cache = {};
44
21
  return async (ctx, next) => {
45
22
  if (ctx.method === 'OPTIONS') {
46
- ctx.status = const_1.NO_CONTENT;
23
+ ctx.status = const_1.HTTP_NO_CONTENT;
47
24
  ctx.set({ Allow: 'OPTIONS, GET' });
48
25
  return;
49
26
  }
50
27
  if (ctx.method !== 'GET')
51
- return ctx.status = const_1.METHOD_NOT_ALLOWED;
28
+ return ctx.status = const_1.HTTP_METHOD_NOT_ALLOWED;
52
29
  const serveApp = shouldServeApp(ctx);
53
30
  const fullPath = (0, path_1.join)(__dirname, '..', DEV_STATIC, folder, serveApp ? '/index.html' : ctx.path);
54
31
  const content = await (0, misc_1.getOrSet)(cache, ctx.path, async () => {
@@ -57,7 +34,7 @@ function serveStatic(uri) {
57
34
  : adjustBundlerLinks(ctx.path, uri, data);
58
35
  });
59
36
  if (content === null)
60
- return ctx.status = 404;
37
+ return ctx.status = const_1.HTTP_NOT_FOUND;
61
38
  if (!serveApp)
62
39
  return (0, serveFile_1.serveFile)(fullPath, 'auto', content)(ctx, next);
63
40
  // we don't cache the index as it's small and may prevent plugins change to apply
@@ -89,7 +66,7 @@ function serveProxied(port, uri) {
89
66
  return;
90
67
  console.debug('proxied on port', port);
91
68
  let proxy;
92
- Promise.resolve().then(() => __importStar(require('koa-better-http-proxy'))).then(lib => // dynamic import to avoid having this in final distribution
69
+ import('koa-better-http-proxy').then(lib => // dynamic import to avoid having this in final distribution
93
70
  proxy = lib.default('127.0.0.1:' + port, {
94
71
  proxyReqPathResolver: (ctx) => shouldServeApp(ctx) ? '/' : ctx.path,
95
72
  userResDecorator(res, data, ctx) {
package/src/sse.js CHANGED
@@ -1,7 +1,8 @@
1
1
  "use strict";
2
- // This file is part of HFS - Copyright 2021-2022, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt
2
+ // This file is part of HFS - Copyright 2021-2023, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt
3
3
  Object.defineProperty(exports, "__esModule", { value: true });
4
4
  const stream_1 = require("stream");
5
+ const const_1 = require("./const");
5
6
  function createSSE(ctx) {
6
7
  const { socket } = ctx.req;
7
8
  socket.setTimeout(0);
@@ -13,7 +14,7 @@ function createSSE(ctx) {
13
14
  'Connection': 'keep-alive',
14
15
  'X-Accel-Buffering': 'no', // avoid buffering when reverse-proxied through nginx
15
16
  });
16
- ctx.status = 200;
17
+ ctx.status = const_1.HTTP_OK;
17
18
  return ctx.body = new stream_1.Transform({
18
19
  objectMode: true,
19
20
  transform(chunk, encoding, cb) {
package/src/throttler.js CHANGED
@@ -1,5 +1,5 @@
1
1
  "use strict";
2
- // This file is part of HFS - Copyright 2021-2022, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt
2
+ // This file is part of HFS - Copyright 2021-2023, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt
3
3
  var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  return (mod && mod.__esModule) ? mod : { "default": mod };
5
5
  };
package/src/update.js CHANGED
@@ -1,4 +1,5 @@
1
1
  "use strict";
2
+ // This file is part of HFS - Copyright 2021-2023, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt
2
3
  Object.defineProperty(exports, "__esModule", { value: true });
3
4
  exports.update = exports.getUpdate = void 0;
4
5
  const github_1 = require("./github");
@@ -29,7 +30,7 @@ async function update() {
29
30
  throw "asset not found";
30
31
  const url = asset.browser_download_url;
31
32
  console.log("downloading", url);
32
- const bin = process.argv[0];
33
+ const bin = process.argv0;
33
34
  const binPath = (0, path_1.dirname)(bin);
34
35
  const binFile = (0, path_1.basename)(bin);
35
36
  const newBinFile = 'new-' + binFile;
@@ -63,7 +64,7 @@ async function update() {
63
64
  }
64
65
  exports.update = update;
65
66
  if (const_1.argv.updating) { // we were launched with a temporary name, restore original name to avoid breaking references
66
- const bin = process.argv[0];
67
+ const bin = process.argv0;
67
68
  (0, fs_1.renameSync)(bin, (0, path_1.join)((0, path_1.dirname)(bin), const_1.argv.updating));
68
69
  console.log("renamed binary file to", const_1.argv.updating);
69
70
  }
package/src/upload.js ADDED
@@ -0,0 +1,92 @@
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
+ exports.uploadWriter = exports.minAvailableMb = exports.deleteUnfinishedUploadsAfter = void 0;
7
+ const vfs_1 = require("./vfs");
8
+ const const_1 = require("./const");
9
+ const path_1 = require("path");
10
+ const fs_1 = __importDefault(require("fs"));
11
+ const misc_1 = require("./misc");
12
+ const frontEndApis_1 = require("./frontEndApis");
13
+ const config_1 = require("./config");
14
+ const util_os_1 = require("./util-os");
15
+ exports.deleteUnfinishedUploadsAfter = (0, config_1.defineConfig)('delete_unfinished_uploads_after');
16
+ exports.minAvailableMb = (0, config_1.defineConfig)('min_available_mb', 100);
17
+ const waitingToBeDeleted = {};
18
+ function uploadWriter(base, path, ctx) {
19
+ if (!base.source || !(0, vfs_1.hasPermission)(base, 'can_upload', ctx))
20
+ return fail(base.can_upload === false ? const_1.HTTP_FORBIDDEN : const_1.HTTP_UNAUTHORIZED);
21
+ const fullPath = (0, path_1.join)(base.source, path);
22
+ const dir = (0, path_1.dirname)(fullPath);
23
+ const min = exports.minAvailableMb.get() * (1 << 20);
24
+ const reqSize = Number(ctx.headers["content-length"]);
25
+ if (min && reqSize)
26
+ try {
27
+ if (reqSize > (0, util_os_1.getFreeDiskSync)(dir) - min)
28
+ return fail(const_1.HTTP_PAYLOAD_TOO_LARGE);
29
+ }
30
+ catch (e) {
31
+ console.warn("can't check disk size", String(e));
32
+ }
33
+ fs_1.default.mkdirSync(dir, { recursive: true });
34
+ const keepName = (0, path_1.basename)(fullPath).slice(-200);
35
+ let tempName = (0, path_1.join)(dir, 'hfs$upload-' + keepName);
36
+ const resumable = fs_1.default.existsSync(tempName) && tempName;
37
+ if (resumable)
38
+ tempName = (0, path_1.join)(dir, 'hfs$upload2-' + keepName);
39
+ const resume = Number(ctx.query.resume);
40
+ const size = resumable && (0, misc_1.try_)(() => fs_1.default.statSync(resumable).size);
41
+ if (size === undefined) // stat failed
42
+ return fail(const_1.HTTP_SERVER_ERROR);
43
+ if (resume > size)
44
+ return fail(const_1.HTTP_RANGE_NOT_SATISFIABLE);
45
+ if (!resume && resumable) {
46
+ const timeout = 30;
47
+ (0, frontEndApis_1.notifyClient)(ctx, 'upload.resumable', { [path]: size, expires: Date.now() + timeout * 1000 });
48
+ delayedDelete(resumable, timeout, () => fs_1.default.rename(tempName, resumable, err => {
49
+ if (!err)
50
+ tempName = resumable;
51
+ }));
52
+ }
53
+ const resuming = resume && resumable;
54
+ const ret = resuming ? fs_1.default.createWriteStream(resumable, { flags: 'r+', start: resume })
55
+ : fs_1.default.createWriteStream(tempName);
56
+ if (resuming) {
57
+ fs_1.default.rm(tempName, () => { });
58
+ tempName = resumable;
59
+ }
60
+ cancelDeletion(tempName);
61
+ ret.on('close', () => {
62
+ if (!ctx.req.aborted)
63
+ return fs_1.default.rename(tempName, fullPath, err => {
64
+ err && console.error("couldn't rename temp to", fullPath, String(err));
65
+ if (resumable)
66
+ delayedDelete(resumable, 0);
67
+ });
68
+ if (resumable) // we don't want to be left with 2 temp files
69
+ return delayedDelete(tempName, 0);
70
+ const sec = exports.deleteUnfinishedUploadsAfter.get();
71
+ if (typeof sec !== 'number')
72
+ return;
73
+ delayedDelete(tempName, sec);
74
+ });
75
+ return ret;
76
+ function delayedDelete(path, secs, cb) {
77
+ clearTimeout(waitingToBeDeleted[path]);
78
+ waitingToBeDeleted[path] = setTimeout(() => {
79
+ delete waitingToBeDeleted[path];
80
+ fs_1.default.rm(path, () => cb === null || cb === void 0 ? void 0 : cb());
81
+ }, secs * 1000);
82
+ }
83
+ function cancelDeletion(path) {
84
+ clearTimeout(waitingToBeDeleted[path]);
85
+ delete waitingToBeDeleted[path];
86
+ }
87
+ function fail(status) {
88
+ ctx.status = status;
89
+ (0, frontEndApis_1.notifyClient)(ctx, 'upload.status', { [path]: ctx.status }); // allow browsers to detect failure while still sending body
90
+ }
91
+ }
92
+ exports.uploadWriter = uploadWriter;
package/src/util-files.js CHANGED
@@ -1,16 +1,17 @@
1
1
  "use strict";
2
+ // This file is part of HFS - Copyright 2021-2023, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt
2
3
  var __importDefault = (this && this.__importDefault) || function (mod) {
3
4
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
5
  };
5
6
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.unzip = exports.run = exports.dirStream = exports.adjustStaticPathForGlob = exports.isWindowsDrive = exports.dirTraversal = exports.watchDir = exports.readFileBusy = exports.isFile = exports.isDirectory = void 0;
7
+ exports.prepareFolder = exports.unzip = exports.dirStream = exports.adjustStaticPathForGlob = exports.isWindowsDrive = exports.dirTraversal = exports.watchDir = exports.readFileBusy = exports.isFile = exports.isDirectory = void 0;
7
8
  const promises_1 = __importDefault(require("fs/promises"));
8
9
  const misc_1 = require("./misc");
9
10
  const fs_1 = require("fs");
10
11
  const path_1 = require("path");
11
12
  const fast_glob_1 = __importDefault(require("fast-glob"));
12
13
  const const_1 = require("./const");
13
- const child_process_1 = require("child_process");
14
+ const util_os_1 = require("./util-os");
14
15
  const stream_1 = require("stream");
15
16
  // @ts-ignore
16
17
  const unzip_stream_1 = __importDefault(require("unzip-stream"));
@@ -114,21 +115,12 @@ async function* dirStream(path, deep) {
114
115
  async function getItemsToSkip(path) {
115
116
  if (!const_1.IS_WINDOWS)
116
117
  return;
117
- const out = await run('dir', ['/ah', '/b', path.replace(/\//g, '\\')])
118
+ const out = await (0, util_os_1.runCmd)('dir', ['/ah', '/b', path.replace(/\//g, '\\')])
118
119
  .catch(() => ''); // error in case of no matching file
119
120
  return out.split('\r\n').slice(0, -1);
120
121
  }
121
122
  }
122
123
  exports.dirStream = dirStream;
123
- function run(cmd, args = []) {
124
- return new Promise((resolve, reject) => (0, child_process_1.execFile)('cmd', ['/c', cmd, ...args], (err, stdout) => {
125
- if (err)
126
- reject(err);
127
- else
128
- resolve(stdout);
129
- }));
130
- }
131
- exports.run = run;
132
124
  async function unzip(stream, cb) {
133
125
  let pending = Promise.resolve();
134
126
  return new Promise(resolve => stream.pipe(unzip_stream_1.default.Parse())
@@ -140,9 +132,23 @@ async function unzip(stream, cb) {
140
132
  return entry.autodrain();
141
133
  await pending; // don't overlap writings
142
134
  console.debug('unzip', dest);
143
- (0, fs_1.mkdirSync)((0, path_1.dirname)(dest), { recursive: true }); // easy way be sure to have the folder ready before proceeding
135
+ await prepareFolder(dest);
144
136
  const thisFile = entry.pipe((0, fs_1.createWriteStream)(dest));
145
137
  pending = (0, stream_1.once)(thisFile, 'finish');
146
138
  }));
147
139
  }
148
140
  exports.unzip = unzip;
141
+ async function prepareFolder(path, dirnameIt = true) {
142
+ if (dirnameIt)
143
+ path = (0, path_1.dirname)(path);
144
+ if (isWindowsDrive(path))
145
+ return;
146
+ try {
147
+ await promises_1.default.mkdir(path, { recursive: true });
148
+ return true;
149
+ }
150
+ catch (_a) {
151
+ return false;
152
+ }
153
+ }
154
+ exports.prepareFolder = prepareFolder;
@@ -1,4 +1,5 @@
1
1
  "use strict";
2
+ // This file is part of HFS - Copyright 2021-2023, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt
2
3
  Object.defineProperty(exports, "__esModule", { value: true });
3
4
  exports.asyncGeneratorToReadable = exports.asyncGeneratorToArray = exports.filterMapGenerator = void 0;
4
5
  // callback can return undefined to skip element
package/src/util-http.js CHANGED
@@ -1,10 +1,12 @@
1
1
  "use strict";
2
+ // This file is part of HFS - Copyright 2021-2023, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt
2
3
  var __importDefault = (this && this.__importDefault) || function (mod) {
3
4
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
5
  };
5
6
  Object.defineProperty(exports, "__esModule", { value: true });
6
7
  exports.httpsStream = exports.httpsString = void 0;
7
8
  const node_https_1 = __importDefault(require("node:https"));
9
+ const const_1 = require("./const");
8
10
  function httpsString(url, options = {}) {
9
11
  return httpsStream(url, options).then(res => new Promise(resolve => {
10
12
  let buf = '';
@@ -21,7 +23,7 @@ function httpsStream(url, options = {}) {
21
23
  node_https_1.default.request(url, options, res => {
22
24
  if (!res.statusCode || res.statusCode >= 400)
23
25
  throw res;
24
- if (res.statusCode === 302 && res.headers.location)
26
+ if (res.statusCode === const_1.HTTP_TEMPORARY_REDIRECT && res.headers.location)
25
27
  return resolve(httpsStream(res.headers.location, options));
26
28
  resolve(res);
27
29
  }).on('error', reject).end();
package/src/util-os.js ADDED
@@ -0,0 +1,41 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.runCmd = exports.getDrives = exports.getFreeDiskSync = void 0;
4
+ const path_1 = require("path");
5
+ const child_process_1 = require("child_process");
6
+ const misc_1 = require("./misc");
7
+ const util_1 = require("util");
8
+ const const_1 = require("./const");
9
+ function getFreeDiskSync(path) {
10
+ var _a;
11
+ if (const_1.IS_WINDOWS) {
12
+ const drive = (0, path_1.resolve)(path).slice(0, 2).toUpperCase();
13
+ const out = (0, child_process_1.execSync)('wmic logicaldisk get FreeSpace,name /format:list').toString().replace(/\r/g, '');
14
+ const one = out.split(/\n\n+/).find(x => x.includes('Name=' + drive));
15
+ if (!one)
16
+ throw Error('miss');
17
+ return Number((_a = /FreeSpace=(\d+)/.exec(one)) === null || _a === void 0 ? void 0 : _a[1]);
18
+ }
19
+ const out = (0, misc_1.try_)(() => (0, child_process_1.execSync)(`df -k ${path}`).toString(), err => {
20
+ throw err.status === 1 ? Error('miss')
21
+ : err.status === 127 ? Error('unsupported')
22
+ : err;
23
+ });
24
+ if (!(out === null || out === void 0 ? void 0 : out.startsWith('Filesystem')))
25
+ throw Error('unsupported');
26
+ const one = out.split('\n')[1];
27
+ const free = Number(one.split(/\s+/)[3]);
28
+ return free * 1024;
29
+ }
30
+ exports.getFreeDiskSync = getFreeDiskSync;
31
+ async function getDrives() {
32
+ const { stdout } = await (0, util_1.promisify)(child_process_1.exec)('wmic logicaldisk get name');
33
+ return stdout.split('\n').slice(1).map(x => x.trim()).filter(Boolean);
34
+ }
35
+ exports.getDrives = getDrives;
36
+ // execute win32 shell commands
37
+ async function runCmd(cmd, args = []) {
38
+ const { stdout, stderr } = await (0, util_1.promisify)(child_process_1.execFile)('cmd', ['/c', cmd, ...args]);
39
+ return stderr || stdout;
40
+ }
41
+ exports.runCmd = runCmd;