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
@@ -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
  var __importDefault = (this && this.__importDefault) || function (mod) {
3
4
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
5
  };
@@ -23,6 +24,7 @@ const apis = {
23
24
  const list = new apiMiddleware_1.SendListReadable({ addAtStart: (0, connections_1.getConnections)().map(c => serializeConnection(c)) });
24
25
  const throttledUpdate = lodash_1.default.throttle(update, 1000 / 20); // try to avoid clogging with updates
25
26
  const state = Symbol('state'); // undefined=added, Timeout=add-pending, false=removed
27
+ list.props({ you: ctx.ip });
26
28
  return list.events(ctx, {
27
29
  connection(conn) {
28
30
  conn[state] = setTimeout(() => add(conn), 100);
@@ -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
  var __importDefault = (this && this.__importDefault) || function (mod) {
3
4
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
5
  };
package/src/api.vfs.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
  };
@@ -10,10 +10,9 @@ const promises_1 = require("fs/promises");
10
10
  const apiMiddleware_1 = require("./apiMiddleware");
11
11
  const path_1 = require("path");
12
12
  const misc_1 = require("./misc");
13
- const child_process_1 = require("child_process");
14
- const util_1 = require("util");
15
13
  const const_1 = require("./const");
16
14
  const micromatch_1 = require("micromatch");
15
+ const util_os_1 = require("./util-os");
17
16
  // to manipulate the tree we need the original node
18
17
  async function urlToNodeOriginal(uri) {
19
18
  const n = await (0, vfs_1.urlToNode)(uri);
@@ -21,7 +20,10 @@ async function urlToNodeOriginal(uri) {
21
20
  }
22
21
  const apis = {
23
22
  async get_vfs() {
24
- return { root: vfs_1.vfs && await recur(vfs_1.vfs) };
23
+ return {
24
+ root: vfs_1.vfs && await recur(vfs_1.vfs),
25
+ defaultPerms: vfs_1.defaultPerms,
26
+ };
25
27
  async function recur(node) {
26
28
  const dir = await (0, vfs_1.nodeIsDirectory)(node);
27
29
  const stats = {};
@@ -49,8 +51,8 @@ const apis = {
49
51
  async set_vfs({ uri, props }) {
50
52
  const n = await urlToNodeOriginal(uri);
51
53
  if (!n)
52
- return new apiMiddleware_1.ApiError(404, 'path not found');
53
- props = pickProps(props, ['name', 'source', 'can_see', 'can_read', 'masks', 'default']);
54
+ return new apiMiddleware_1.ApiError(const_1.HTTP_NOT_FOUND, 'path not found');
55
+ props = pickProps(props, ['name', 'source', 'masks', 'default', ...Object.keys(vfs_1.defaultPerms)]);
54
56
  props = (0, misc_1.objSameKeys)(props, v => v === null ? undefined : v); // null is a way to serialize undefined, that will restore default values
55
57
  if (props.masks && typeof props.masks !== 'object')
56
58
  delete props.masks;
@@ -63,35 +65,35 @@ const apis = {
63
65
  async add_vfs({ under, source, name }) {
64
66
  const n = under ? await urlToNodeOriginal(under) : vfs_1.vfs;
65
67
  if (!n)
66
- return new apiMiddleware_1.ApiError(404, 'invalid under');
68
+ return new apiMiddleware_1.ApiError(const_1.HTTP_NOT_FOUND, 'invalid under');
67
69
  if (n.isTemp || !await (0, vfs_1.nodeIsDirectory)(n))
68
- return new apiMiddleware_1.ApiError(const_1.FORBIDDEN, 'invalid under');
70
+ return new apiMiddleware_1.ApiError(const_1.HTTP_NOT_ACCEPTABLE, 'invalid under');
69
71
  if ((0, misc_1.isWindowsDrive)(source))
70
72
  source += '\\'; // slash must be included, otherwise it will refer to the cwd of that drive
71
73
  const a = n.children || (n.children = []);
72
74
  if (source && a.find(x => x.source === source))
73
- return new apiMiddleware_1.ApiError(409, 'already present');
75
+ return new apiMiddleware_1.ApiError(const_1.HTTP_CONFLICT, 'already present');
74
76
  a.unshift({ source, name });
75
77
  await (0, vfs_1.saveVfs)();
76
78
  return {};
77
79
  },
78
80
  async del_vfs({ uris }) {
79
81
  if (!uris || !Array.isArray(uris))
80
- return new apiMiddleware_1.ApiError(400, 'invalid uris');
82
+ return new apiMiddleware_1.ApiError(const_1.HTTP_BAD_REQUEST, 'invalid uris');
81
83
  return {
82
84
  errors: await Promise.all(uris.map(async (uri) => {
83
85
  if (typeof uri !== 'string')
84
- return 400;
86
+ return const_1.HTTP_BAD_REQUEST;
85
87
  const node = await urlToNodeOriginal(uri);
86
88
  if (!node)
87
- return 404;
89
+ return const_1.HTTP_NOT_FOUND;
88
90
  const parent = (0, path_1.dirname)(uri);
89
91
  const parentNode = await urlToNodeOriginal(parent);
90
92
  if (!parentNode)
91
- return const_1.FORBIDDEN;
93
+ return const_1.HTTP_NOT_ACCEPTABLE;
92
94
  const { children } = parentNode;
93
95
  if (!children) // shouldn't happen
94
- return 500;
96
+ return const_1.HTTP_SERVER_ERROR;
95
97
  const idx = children.indexOf(node);
96
98
  children.splice(idx, 1);
97
99
  (0, vfs_1.saveVfs)();
@@ -112,7 +114,7 @@ const apis = {
112
114
  async *ls({ path, files = true, fileMask }, ctx) {
113
115
  if (!path && const_1.IS_WINDOWS) {
114
116
  try {
115
- for (const n of await getDrives())
117
+ for (const n of await (0, util_os_1.getDrives)())
116
118
  yield { add: { n, k: 'd' } };
117
119
  }
118
120
  catch (error) {
@@ -158,7 +160,3 @@ function pickProps(o, keys) {
158
160
  ret[k] = o[k] === null || o[k] === '' ? undefined : o[k];
159
161
  return ret;
160
162
  }
161
- async function getDrives() {
162
- const { stdout } = await (0, util_1.promisify)(child_process_1.exec)('wmic logicaldisk get name');
163
- return stdout.split('\n').slice(1).map(x => x.trim()).filter(Boolean);
164
- }
@@ -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
  };
@@ -13,7 +13,7 @@ const const_1 = require("./const");
13
13
  const lodash_1 = __importDefault(require("lodash"));
14
14
  class ApiError extends Error {
15
15
  constructor(status, message) {
16
- super(typeof message === 'string' ? message : message === null || message === void 0 ? void 0 : message.message);
16
+ super(typeof message === 'string' ? message : message && message instanceof Error ? message.message : JSON.stringify(message));
17
17
  this.status = status;
18
18
  }
19
19
  }
@@ -22,21 +22,25 @@ function apiMiddleware(apis) {
22
22
  return async (ctx) => {
23
23
  const { params } = ctx;
24
24
  console.debug('API', ctx.method, ctx.path, { ...params });
25
- if (!apis.hasOwnProperty(ctx.path)) {
25
+ const apiFun = apis.hasOwnProperty(ctx.path) && apis[ctx.path];
26
+ if (!apiFun) {
26
27
  ctx.body = 'invalid api';
27
- return ctx.status = 404;
28
+ return ctx.status = const_1.HTTP_NOT_FOUND;
28
29
  }
30
+ ctx.params = ctx.method === 'POST' ? (0, misc_1.tryJson)(await (0, misc_1.stream2string)(ctx.req))
31
+ : (0, misc_1.objSameKeys)(ctx.query, x => Array.isArray(x) ? x : (0, misc_1.tryJson)(x));
32
+ console.debug('API', ctx.method, ctx.path, { ...ctx.params });
29
33
  const csrf = ctx.cookies.get('csrf');
30
34
  // we don't rely on SameSite cookie option because it's https-only
31
- let res = csrf && csrf !== params.csrf ? new ApiError(const_1.UNAUTHORIZED, 'csrf')
32
- : await apis[ctx.path](params || {}, ctx);
35
+ let res = csrf && csrf !== ctx.params.csrf ? new ApiError(const_1.HTTP_UNAUTHORIZED, 'csrf')
36
+ : await apiFun(ctx.params || {}, ctx);
33
37
  if (isAsyncGenerator(res))
34
38
  res = (0, misc_1.asyncGeneratorToReadable)(res);
35
39
  if (res instanceof stream_1.Readable) { // Readable, we'll go SSE-mode
36
40
  res.pipe((0, sse_1.default)(ctx));
37
- const stillRes = res; // satisfy ts
41
+ const resAsReadable = res; // satisfy ts
38
42
  ctx.req.on('close', () => // by closing the generated stream, creator of the stream will know the request is over without having to access anything else
39
- stillRes.destroy());
43
+ resAsReadable.destroy());
40
44
  return;
41
45
  }
42
46
  if (res instanceof ApiError) {
@@ -45,7 +49,7 @@ function apiMiddleware(apis) {
45
49
  }
46
50
  if (res instanceof Error) { // generic exception
47
51
  ctx.body = String(res);
48
- return ctx.status = 400;
52
+ return ctx.status = const_1.HTTP_BAD_REQUEST;
49
53
  }
50
54
  ctx.body = res;
51
55
  };
@@ -95,6 +99,9 @@ class SendListReadable extends stream_1.Readable {
95
99
  custom(data) {
96
100
  this._push(data);
97
101
  }
102
+ props(props) {
103
+ this._push({ props });
104
+ }
98
105
  error(msg, close = false) {
99
106
  this._push({ error: msg });
100
107
  this.lastError = msg;
package/src/block.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
  var __importDefault = (this && this.__importDefault) || function (mod) {
3
4
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
5
  };
package/src/commands.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
  var __importDefault = (this && this.__importDefault) || function (mod) {
3
4
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
5
  };
package/src/config.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
  };
@@ -147,7 +147,8 @@ function setConfig1(k, newV, saveChanges = true) {
147
147
  var _a;
148
148
  if (lodash_1.default.isPlainObject(newV))
149
149
  newV = lodash_1.default.pickBy(newV, x => x !== undefined);
150
- if ((0, misc_1.same)(newV, (_a = configProps[k]) === null || _a === void 0 ? void 0 : _a.defaultValue))
150
+ const def = (_a = configProps[k]) === null || _a === void 0 ? void 0 : _a.defaultValue;
151
+ if ((0, misc_1.same)(newV !== null && newV !== void 0 ? newV : null, def !== null && def !== void 0 ? def : null))
151
152
  newV = undefined;
152
153
  if (started && (0, misc_1.same)(newV, state[k]))
153
154
  return; // no change
@@ -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/const.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);
@@ -27,7 +27,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
27
27
  return (mod && mod.__esModule) ? mod : { "default": mod };
28
28
  };
29
29
  Object.defineProperty(exports, "__esModule", { value: true });
30
- exports.APP_PATH = exports.IS_WINDOWS = exports.UNAUTHORIZED = exports.FORBIDDEN = exports.NO_CONTENT = exports.METHOD_NOT_ALLOWED = exports.PLUGINS_PUB_URI = exports.API_URI = exports.ADMIN_URI = exports.FRONTEND_URI = exports.SPECIAL_URI = exports.COMPATIBLE_API_VERSION = exports.API_VERSION = exports.SESSION_DURATION = exports.DAY = exports.VERSION = exports.BUILD_TIMESTAMP = exports.HFS_STARTED = exports.ORIGINAL_CWD = exports.DEV = exports.argv = void 0;
30
+ exports.APP_PATH = exports.IS_WINDOWS = exports.HTTP_SERVER_ERROR = exports.HTTP_FOOL = exports.HTTP_RANGE_NOT_SATISFIABLE = exports.HTTP_PAYLOAD_TOO_LARGE = exports.HTTP_CONFLICT = exports.HTTP_NOT_ACCEPTABLE = exports.HTTP_METHOD_NOT_ALLOWED = exports.HTTP_NOT_FOUND = exports.HTTP_FORBIDDEN = exports.HTTP_UNAUTHORIZED = exports.HTTP_BAD_REQUEST = exports.HTTP_NOT_MODIFIED = exports.HTTP_TEMPORARY_REDIRECT = exports.HTTP_PARTIAL_CONTENT = exports.HTTP_NO_CONTENT = exports.HTTP_OK = exports.PLUGINS_PUB_URI = exports.API_URI = exports.ADMIN_URI = exports.FRONTEND_URI = exports.SPECIAL_URI = exports.COMPATIBLE_API_VERSION = exports.API_VERSION = exports.SESSION_DURATION = exports.DAY = exports.VERSION = exports.BUILD_TIMESTAMP = exports.HFS_STARTED = exports.ORIGINAL_CWD = exports.DEV = exports.argv = void 0;
31
31
  const minimist_1 = __importDefault(require("minimist"));
32
32
  const fs = __importStar(require("fs"));
33
33
  const os_1 = require("os");
@@ -50,10 +50,22 @@ exports.FRONTEND_URI = exports.SPECIAL_URI + 'frontend/';
50
50
  exports.ADMIN_URI = exports.SPECIAL_URI + 'admin/';
51
51
  exports.API_URI = exports.SPECIAL_URI + 'api/';
52
52
  exports.PLUGINS_PUB_URI = exports.SPECIAL_URI + 'plugins/';
53
- exports.METHOD_NOT_ALLOWED = 405;
54
- exports.NO_CONTENT = 204;
55
- exports.FORBIDDEN = 403;
56
- exports.UNAUTHORIZED = 401;
53
+ exports.HTTP_OK = 200;
54
+ exports.HTTP_NO_CONTENT = 204;
55
+ exports.HTTP_PARTIAL_CONTENT = 206;
56
+ exports.HTTP_TEMPORARY_REDIRECT = 302;
57
+ exports.HTTP_NOT_MODIFIED = 304;
58
+ exports.HTTP_BAD_REQUEST = 400;
59
+ exports.HTTP_UNAUTHORIZED = 401;
60
+ exports.HTTP_FORBIDDEN = 403;
61
+ exports.HTTP_NOT_FOUND = 404;
62
+ exports.HTTP_METHOD_NOT_ALLOWED = 405;
63
+ exports.HTTP_NOT_ACCEPTABLE = 406;
64
+ exports.HTTP_CONFLICT = 409;
65
+ exports.HTTP_PAYLOAD_TOO_LARGE = 413;
66
+ exports.HTTP_RANGE_NOT_SATISFIABLE = 416;
67
+ exports.HTTP_FOOL = 418;
68
+ exports.HTTP_SERVER_ERROR = 500;
57
69
  exports.IS_WINDOWS = process.platform === 'win32';
58
70
  const IS_BINARY = !(0, path_1.basename)(process.argv0).includes('node'); // this won't be node if pkg was used
59
71
  exports.APP_PATH = (0, path_1.dirname)(IS_BINARY ? process.argv0 : __dirname);
@@ -62,7 +74,7 @@ if (exports.DEV)
62
74
  console.clear();
63
75
  else
64
76
  console.debug = () => { };
65
- console.log(`HFS ~ HTTP File Server - Copyright 2021-2022, Massimo Melina <a@rejetto.com>`);
77
+ console.log(`HFS ~ HTTP File Server - Copyright 2021-2023, Massimo Melina <a@rejetto.com>`);
66
78
  console.log(`License https://www.gnu.org/licenses/gpl-3.0.txt`);
67
79
  console.log('started', exports.HFS_STARTED.toLocaleString(), exports.DEV);
68
80
  console.log('version', exports.VERSION || '-');
package/src/crypt.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
  };
@@ -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
  // like lodash.debounce, but also avoids async invocations to overlap
4
5
  function debounceAsync(callback, wait = 100, { leading = false, maxWait = Infinity } = {}) {
package/src/events.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
  };
@@ -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);
@@ -23,16 +23,37 @@ var __importStar = (this && this.__importStar) || function (mod) {
23
23
  __setModuleDefault(result, mod);
24
24
  return result;
25
25
  };
26
+ var __importDefault = (this && this.__importDefault) || function (mod) {
27
+ return (mod && mod.__esModule) ? mod : { "default": mod };
28
+ };
26
29
  Object.defineProperty(exports, "__esModule", { value: true });
27
- exports.frontEndApis = void 0;
30
+ exports.notifyClient = exports.frontEndApis = void 0;
31
+ const apiMiddleware_1 = require("./apiMiddleware");
28
32
  const api_file_list_1 = require("./api.file_list");
29
33
  const api_auth = __importStar(require("./api.auth"));
30
34
  const config_1 = require("./config");
35
+ const events_1 = __importDefault(require("./events"));
31
36
  const customHeader = (0, config_1.defineConfig)('custom_header');
32
37
  exports.frontEndApis = {
33
38
  file_list: api_file_list_1.file_list,
34
39
  ...api_auth,
35
40
  config() {
36
41
  return Object.fromEntries([customHeader].map(x => [x.key(), x.get()]));
42
+ },
43
+ get_notifications({ channel }, ctx) {
44
+ const list = new apiMiddleware_1.SendListReadable();
45
+ list.ready(); // on chrome109 EventSource doesn't emit 'open' until something is sent
46
+ return list.events(ctx, {
47
+ [NOTIFICATION_PREFIX + channel](name, data) {
48
+ list.custom({ name, data });
49
+ }
50
+ });
37
51
  }
38
52
  };
53
+ function notifyClient(ctx, name, data) {
54
+ const { notificationChannel } = ctx.query;
55
+ if (notificationChannel)
56
+ events_1.default.emit(NOTIFICATION_PREFIX + notificationChannel, name, data);
57
+ }
58
+ exports.notifyClient = notifyClient;
59
+ const NOTIFICATION_PREFIX = 'notificationChannel:';
package/src/github.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
  var __importDefault = (this && this.__importDefault) || function (mod) {
3
4
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
5
  };
@@ -9,6 +10,7 @@ const misc_1 = require("./misc");
9
10
  const plugins_1 = require("./plugins");
10
11
  const apiMiddleware_1 = require("./apiMiddleware");
11
12
  const lodash_1 = __importDefault(require("lodash"));
13
+ const const_1 = require("./const");
12
14
  const DIST_ROOT = 'dist/';
13
15
  const downloading = {};
14
16
  function downloadProgress(id, status) {
@@ -20,12 +22,14 @@ function downloadProgress(id, status) {
20
22
  }
21
23
  async function downloadPlugin(repo, branch = '', overwrite) {
22
24
  if (downloading[repo])
23
- return new apiMiddleware_1.ApiError(409, "already downloading");
25
+ return new apiMiddleware_1.ApiError(const_1.HTTP_CONFLICT, "already downloading");
24
26
  downloadProgress(repo, true);
25
27
  const rec = await getRepoInfo(repo);
26
28
  if (!branch)
27
29
  branch = rec.default_branch;
28
30
  const short = repo.split('/')[1]; // second part, repo without the owner
31
+ if (!short)
32
+ return new apiMiddleware_1.ApiError(const_1.HTTP_BAD_REQUEST, "bad repo");
29
33
  const folder2repo = getFolder2repo();
30
34
  const folder = overwrite ? lodash_1.default.findKey(folder2repo, x => x === repo) // use existing folder
31
35
  : short in folder2repo ? repo.replace('/', '-') // longer form only if another plugin is using short form
package/src/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  "use strict";
3
- // 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
+ // This file is part of HFS - Copyright 2021-2023, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt
4
4
  var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  return (mod && mod.__esModule) ? mod : { "default": mod };
6
6
  };
@@ -23,6 +23,7 @@ const config_1 = require("./config");
23
23
  const assert_1 = require("assert");
24
24
  const lodash_1 = __importDefault(require("lodash"));
25
25
  const misc_1 = require("./misc");
26
+ //import body from 'koa-better-body'
26
27
  (0, assert_1.ok)(lodash_1.default.intersection(Object.keys(frontEndApis_1.frontEndApis), Object.keys(adminApis_1.adminApis)).length === 0); // they share same endpoints
27
28
  const keys = ((_a = process.env.COOKIE_SIGN_KEYS) === null || _a === void 0 ? void 0 : _a.split(',')) || [(0, misc_1.randomId)(30)];
28
29
  exports.app = new koa_1.default({ keys });
@@ -33,16 +34,17 @@ exports.app.use(middlewares_1.someSecurity)
33
34
  .use((0, log_1.log)())
34
35
  .use(throttler_1.throttler)
35
36
  .use(middlewares_1.gzipper)
36
- .use(middlewares_1.paramsDecoder)
37
37
  .use((0, plugins_1.pluginsMiddleware)())
38
38
  .use((0, koa_mount_1.default)(const_1.API_URI, (0, apiMiddleware_1.apiMiddleware)({ ...frontEndApis_1.frontEndApis, ...adminApis_1.adminApis })))
39
+ //.use(body({ multipart: false }))
39
40
  .use(middlewares_1.serveGuiAndSharedFiles)
40
41
  .on('error', errorHandler);
41
42
  function errorHandler(err) {
42
43
  const { code } = err;
43
44
  if (const_1.DEV && code === 'ENOENT' && err.path.endsWith('sockjs-node'))
44
45
  return; // spam out dev stuff
45
- if (code === 'ECANCELED' || code === 'ECONNRESET' || code === 'ECONNABORTED' || code === 'EPIPE')
46
+ if (code === 'ECANCELED' || code === 'ECONNRESET' || code === 'ECONNABORTED' || code === 'EPIPE'
47
+ || code === 'HPE_INVALID_EOF_STATE')
46
48
  return; // someone interrupted, don't care
47
49
  console.error('server error', err);
48
50
  }
package/src/listen.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);
@@ -132,6 +132,8 @@ function startServer(srv, { port, host }) {
132
132
  try {
133
133
  if (port < 0 || !host && !await testIpV4()) // !host means ipV4+6, and if v4 port alone is busy we won't be notified of the failure, so we'll first test it on its own
134
134
  return resolve(0);
135
+ // from a few tests, this seems enough to support the expect-100 http/1.1 mechanism, at least with curl -T, not used by chrome|firefox anyway
136
+ srv.on('checkContinue', (req, res) => srv.emit('request', req, res));
135
137
  port = await listen(host);
136
138
  if (port)
137
139
  console.log(srv.name, "serving on", host || "any network", ':', port);
@@ -223,9 +225,10 @@ function printUrls(port, proto) {
223
225
  if (!nets || ignore.test(name))
224
226
  continue;
225
227
  lodash_1.default.remove(nets, 'internal');
226
- if (!nets.length)
228
+ const first = nets[0];
229
+ if (!first)
227
230
  continue;
228
- const best = lodash_1.default.find(nets, { family: 'IPv4' }) || nets[0];
231
+ const best = lodash_1.default.find(nets, { family: 'IPv4' }) || first;
229
232
  const appendPort = port === (proto === 'https' ? 443 : 80) ? '' : ':' + port;
230
233
  let { address } = best;
231
234
  if (address.includes(':'))
package/src/log.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);
@@ -35,7 +35,7 @@ const promises_1 = require("fs/promises");
35
35
  const const_1 = require("./const");
36
36
  const events_1 = __importDefault(require("./events"));
37
37
  const lodash_1 = __importDefault(require("lodash"));
38
- const path_1 = require("path");
38
+ const util_files_1 = require("./util-files");
39
39
  const perm_1 = require("./perm");
40
40
  class Logger {
41
41
  constructor(name) {
@@ -54,8 +54,8 @@ class Logger {
54
54
  this.last = stats.mtime || stats.ctime;
55
55
  }
56
56
  catch (_b) {
57
- await (0, promises_1.mkdir)((0, path_1.dirname)(path), { recursive: true })
58
- .catch(() => console.log("cannot create folder for", path));
57
+ if (await (0, util_files_1.prepareFolder)(path) === false)
58
+ console.log("cannot create folder for", path);
59
59
  }
60
60
  this.reopen();
61
61
  }
@@ -81,7 +81,7 @@ const logRotation = (0, config_1.defineConfig)('log_rotation', 'weekly');
81
81
  function log() {
82
82
  const debounce = lodash_1.default.debounce(cb => cb(), 1000);
83
83
  return async (ctx, next) => {
84
- var _a;
84
+ var _a, _b;
85
85
  await next();
86
86
  const isError = ctx.status >= 400;
87
87
  const logger = isError && accessErrorLog || accessLogger;
@@ -112,7 +112,7 @@ function log() {
112
112
  }
113
113
  }
114
114
  const format = '%s - %s [%s] "%s %s HTTP/%s" %d %s\n'; // Apache's Common Log Format
115
- const date = a[2] + '/' + a[1] + '/' + a[3] + ':' + a[4] + ' ' + a[5].slice(3);
115
+ const date = a[2] + '/' + a[1] + '/' + a[3] + ':' + a[4] + ' ' + ((_b = a[5]) === null || _b === void 0 ? void 0 : _b.slice(3));
116
116
  const user = (0, perm_1.getCurrentUsername)(ctx);
117
117
  events_1.default.emit(logger.name, Object.assign(lodash_1.default.pick(ctx, ['ip', 'method', 'status', 'length']), { user, ts: now, uri: ctx.path }));
118
118
  console.debug(ctx.status, ctx.method, ctx.path);
@@ -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.paramsDecoder = exports.prepareState = exports.getProxyDetected = exports.someSecurity = exports.serveGuiAndSharedFiles = exports.sessions = exports.headRequests = exports.gzipper = void 0;
7
+ exports.prepareState = exports.getProxyDetected = exports.someSecurity = exports.serveGuiAndSharedFiles = exports.sessions = exports.headRequests = exports.gzipper = void 0;
8
8
  const koa_compress_1 = __importDefault(require("koa-compress"));
9
9
  const koa_session_1 = __importDefault(require("koa-session"));
10
10
  const const_1 = require("./const");
@@ -22,6 +22,10 @@ const connections_1 = require("./connections");
22
22
  const basic_auth_1 = __importDefault(require("basic-auth"));
23
23
  const tssrp6a_1 = require("tssrp6a");
24
24
  const api_auth_1 = require("./api.auth");
25
+ const path_1 = require("path");
26
+ const promises_1 = require("stream/promises");
27
+ const formidable_1 = __importDefault(require("formidable"));
28
+ const upload_1 = require("./upload");
25
29
  exports.gzipper = (0, koa_compress_1.default)({
26
30
  threshold: 2048,
27
31
  gzip: { flush: require('zlib').constants.Z_SYNC_FLUSH },
@@ -66,9 +70,33 @@ const serveGuiAndSharedFiles = async (ctx, next) => {
66
70
  return ctx.redirect(const_1.ADMIN_URI);
67
71
  if (path.startsWith(const_1.ADMIN_URI))
68
72
  return serveAdminPrefixed(ctx, next);
73
+ if (ctx.method === 'PUT') { // curl -T file url/
74
+ const decPath = decodeURI(path);
75
+ let rest = (0, path_1.basename)(decPath);
76
+ const folder = await (0, vfs_1.urlToNode)((0, path_1.dirname)(decPath), ctx, vfs_1.vfs, v => rest = v + '/' + rest);
77
+ if (!folder)
78
+ return ctx.status = const_1.HTTP_NOT_FOUND;
79
+ const dest = (0, upload_1.uploadWriter)(folder, rest, ctx);
80
+ if (dest) {
81
+ await (0, promises_1.pipeline)(ctx.req, dest);
82
+ ctx.body = {};
83
+ }
84
+ return;
85
+ }
69
86
  const node = await (0, vfs_1.urlToNode)(path, ctx);
70
87
  if (!node)
71
- return ctx.status = 404;
88
+ return ctx.status = const_1.HTTP_NOT_FOUND;
89
+ if (ctx.method === 'POST') { // curl -F upload=@file url/
90
+ ctx.body = {};
91
+ const form = (0, formidable_1.default)({
92
+ maxFileSize: Infinity,
93
+ //@ts-ignore wrong in the .d.ts file
94
+ fileWriteStreamHandler: f => (0, upload_1.uploadWriter)(node, f.originalFilename, ctx)
95
+ });
96
+ form.parse(ctx.req);
97
+ await (0, stream_1.once)(form, 'end').catch(() => { });
98
+ return;
99
+ }
72
100
  const canRead = (0, vfs_1.hasPermission)(node, 'can_read', ctx);
73
101
  const isFolder = await (0, vfs_1.nodeIsDirectory)(node);
74
102
  if (isFolder && !path.endsWith('/'))
@@ -78,7 +106,7 @@ const serveGuiAndSharedFiles = async (ctx, next) => {
78
106
  : next();
79
107
  if (!canRead) {
80
108
  ctx.status = (0, vfs_1.cantReadStatusCode)(node);
81
- if (ctx.status === const_1.FORBIDDEN)
109
+ if (ctx.status === const_1.HTTP_FORBIDDEN)
82
110
  return;
83
111
  const browserDetected = ctx.get('Upgrade-Insecure-Requests') || ctx.get('Sec-Fetch-Mode'); // ugh, heuristics
84
112
  if (!browserDetected) // we don't want to trigger basic authentication on browsers, it's meant for download managers only
@@ -151,25 +179,3 @@ async function srpCheck(username, password) {
151
179
  const clientRes2 = await clientRes1.step2(BigInt(salt), BigInt(pubKey));
152
180
  return await step1.step2(clientRes2.A, clientRes2.M1).then(() => true, () => false);
153
181
  }
154
- // unify get/post parameters, with JSON decoding to not be limited to strings
155
- const paramsDecoder = async (ctx, next) => {
156
- ctx.params = ctx.method === 'POST' ? (0, misc_1.tryJson)(await getReqData(ctx.req))
157
- : (0, misc_1.objSameKeys)(ctx.query, x => Array.isArray(x) ? x : (0, misc_1.tryJson)(x));
158
- await next();
159
- };
160
- exports.paramsDecoder = paramsDecoder;
161
- async function getReqData(req) {
162
- return new Promise((resolve, reject) => {
163
- let data = '';
164
- req.on('data', chunk => data += chunk);
165
- req.on('error', reject);
166
- req.on('end', () => {
167
- try {
168
- resolve(data);
169
- }
170
- catch (e) {
171
- reject(e);
172
- }
173
- });
174
- });
175
- }
package/src/misc.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);
@@ -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.tryJson = exports.same = exports.isLocalHost = exports.with_ = exports.typedKeys = exports.objRenameKey = exports.onOff = exports.pendingPromise = exports.onlyTruthy = exports.truthy = exports.pattern2filter = exports.onFirstEvent = exports.onProcessExit = exports.randomId = exports.getOrSet = exports.wantArray = exports.wait = exports.objSameKeys = exports.setHidden = exports.prefix = exports.enforceFinal = exports.debounceAsync = void 0;
21
+ exports.try_ = exports.stream2string = exports.tryJson = exports.same = exports.isLocalHost = exports.with_ = exports.typedKeys = exports.objRenameKey = exports.onOff = exports.pendingPromise = exports.onlyTruthy = exports.truthy = exports.pattern2filter = exports.onFirstEvent = exports.onProcessExit = exports.randomId = exports.getOrSet = exports.wantArray = exports.wait = exports.objSameKeys = exports.setHidden = exports.prefix = exports.enforceFinal = exports.debounceAsync = void 0;
22
22
  const path_1 = require("path");
23
23
  const lodash_1 = __importDefault(require("lodash"));
24
24
  const assert_1 = __importDefault(require("assert"));
@@ -158,3 +158,28 @@ function tryJson(s) {
158
158
  catch (_a) { }
159
159
  }
160
160
  exports.tryJson = tryJson;
161
+ async function stream2string(stream) {
162
+ return new Promise((resolve, reject) => {
163
+ let data = '';
164
+ stream.on('data', chunk => data += chunk);
165
+ stream.on('error', reject);
166
+ stream.on('end', () => {
167
+ try {
168
+ resolve(data);
169
+ }
170
+ catch (e) {
171
+ reject(e);
172
+ }
173
+ });
174
+ });
175
+ }
176
+ exports.stream2string = stream2string;
177
+ function try_(cb, onException) {
178
+ try {
179
+ return cb();
180
+ }
181
+ catch (e) {
182
+ return onException === null || onException === void 0 ? void 0 : onException(e);
183
+ }
184
+ }
185
+ exports.try_ = try_;