hfs 0.26.8 → 0.27.2

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 (163) hide show
  1. package/README.md +15 -2
  2. package/admin/assets/index-509bb1d6.js +415 -0
  3. package/admin/assets/index-60a380a7.css +1 -0
  4. package/admin/assets/sha512-738f0943.js +8 -0
  5. package/admin/index.html +3 -1
  6. package/admin/{public/logo.svg → logo.svg} +0 -0
  7. package/frontend/assets/index-6e178dfd.css +1 -0
  8. package/frontend/assets/index-aea7654e.js +85 -0
  9. package/frontend/assets/sha512-bf915587.js +8 -0
  10. package/frontend/{public/fontello.css → fontello.css} +0 -0
  11. package/frontend/{public/fontello.woff2 → fontello.woff2} +0 -0
  12. package/frontend/index.html +4 -2
  13. package/package.json +2 -6
  14. package/plugins/vhosting/plugin.js +23 -20
  15. package/src/QuickZipStream.js +285 -0
  16. package/src/ThrottledStream.js +93 -0
  17. package/src/adminApis.js +169 -0
  18. package/src/api.accounts.js +59 -0
  19. package/src/api.auth.js +128 -0
  20. package/src/api.file_list.js +110 -0
  21. package/src/api.helpers.js +32 -0
  22. package/src/api.monitor.js +104 -0
  23. package/src/api.plugins.js +128 -0
  24. package/src/api.vfs.js +167 -0
  25. package/src/apiMiddleware.js +123 -0
  26. package/src/block.js +34 -0
  27. package/src/commands.js +125 -0
  28. package/src/config.js +168 -0
  29. package/src/connections.js +57 -0
  30. package/src/const.js +94 -0
  31. package/src/crypt.js +21 -0
  32. package/src/debounceAsync.js +49 -0
  33. package/src/events.js +9 -0
  34. package/src/frontEndApis.js +38 -0
  35. package/src/github.js +104 -0
  36. package/src/index.js +57 -0
  37. package/src/listen.js +235 -0
  38. package/src/log.js +137 -0
  39. package/src/middlewares.js +195 -0
  40. package/src/misc.js +160 -0
  41. package/src/pbkdf2.js +74 -0
  42. package/src/perm.js +183 -0
  43. package/src/plugins.js +343 -0
  44. package/src/serveFile.js +105 -0
  45. package/src/serveGuiFiles.js +113 -0
  46. package/src/sse.js +30 -0
  47. package/src/throttler.js +91 -0
  48. package/src/update.js +70 -0
  49. package/src/util-files.js +163 -0
  50. package/src/util-generators.js +31 -0
  51. package/src/util-http.js +32 -0
  52. package/src/vfs.js +232 -0
  53. package/src/watchLoad.js +73 -0
  54. package/src/zip.js +73 -0
  55. package/admin/.DS_Store +0 -0
  56. package/admin/.eslintrc +0 -8
  57. package/admin/.gitignore +0 -23
  58. package/admin/package.json +0 -67
  59. package/admin/src/AccountForm.ts +0 -92
  60. package/admin/src/AccountsPage.ts +0 -143
  61. package/admin/src/App.ts +0 -83
  62. package/admin/src/ArrayField.ts +0 -84
  63. package/admin/src/ConfigPage.ts +0 -279
  64. package/admin/src/FileField.ts +0 -52
  65. package/admin/src/FileForm.ts +0 -148
  66. package/admin/src/FilePicker.ts +0 -166
  67. package/admin/src/HomePage.ts +0 -96
  68. package/admin/src/InstalledPlugins.ts +0 -158
  69. package/admin/src/LoginRequired.ts +0 -75
  70. package/admin/src/LogoutPage.ts +0 -27
  71. package/admin/src/LogsPage.ts +0 -75
  72. package/admin/src/MainMenu.ts +0 -74
  73. package/admin/src/MenuButton.ts +0 -38
  74. package/admin/src/MonitorPage.ts +0 -200
  75. package/admin/src/OnlinePlugins.ts +0 -101
  76. package/admin/src/PermField.ts +0 -80
  77. package/admin/src/PluginsPage.ts +0 -27
  78. package/admin/src/VfsMenuBar.ts +0 -58
  79. package/admin/src/VfsPage.ts +0 -124
  80. package/admin/src/VfsTree.ts +0 -95
  81. package/admin/src/addFiles.ts +0 -59
  82. package/admin/src/api.ts +0 -246
  83. package/admin/src/dialog.ts +0 -203
  84. package/admin/src/index.css +0 -21
  85. package/admin/src/index.ts +0 -10
  86. package/admin/src/md.ts +0 -31
  87. package/admin/src/misc.ts +0 -141
  88. package/admin/src/react-app-env.d.ts +0 -1
  89. package/admin/src/reportWebVitals.ts +0 -15
  90. package/admin/src/setupTests.ts +0 -5
  91. package/admin/src/state.ts +0 -40
  92. package/admin/src/theme.ts +0 -37
  93. package/admin/tsconfig.json +0 -26
  94. package/admin/vite.config.ts +0 -32
  95. package/frontend/.DS_Store +0 -0
  96. package/frontend/.eslintrc +0 -8
  97. package/frontend/.gitignore +0 -23
  98. package/frontend/package.json +0 -51
  99. package/frontend/src/App.ts +0 -25
  100. package/frontend/src/Breadcrumbs.ts +0 -43
  101. package/frontend/src/BrowseFiles.ts +0 -141
  102. package/frontend/src/Head.ts +0 -45
  103. package/frontend/src/UserPanel.ts +0 -52
  104. package/frontend/src/api.ts +0 -78
  105. package/frontend/src/components.ts +0 -54
  106. package/frontend/src/dialog.css +0 -76
  107. package/frontend/src/dialog.ts +0 -105
  108. package/frontend/src/icons.ts +0 -46
  109. package/frontend/src/index.scss +0 -307
  110. package/frontend/src/index.ts +0 -10
  111. package/frontend/src/login.ts +0 -50
  112. package/frontend/src/menu.ts +0 -188
  113. package/frontend/src/misc.ts +0 -54
  114. package/frontend/src/options.ts +0 -52
  115. package/frontend/src/react-app-env.d.ts +0 -1
  116. package/frontend/src/reportWebVitals.ts +0 -15
  117. package/frontend/src/setupTests.ts +0 -5
  118. package/frontend/src/state.ts +0 -82
  119. package/frontend/src/useAuthorized.ts +0 -17
  120. package/frontend/src/useFetchList.ts +0 -144
  121. package/frontend/src/useTheme.ts +0 -23
  122. package/frontend/tsconfig.json +0 -26
  123. package/frontend/vite.config.ts +0 -21
  124. package/src/QuickZipStream.ts +0 -279
  125. package/src/ThrottledStream.ts +0 -98
  126. package/src/adminApis.ts +0 -161
  127. package/src/api.accounts.ts +0 -78
  128. package/src/api.auth.ts +0 -131
  129. package/src/api.file_list.ts +0 -102
  130. package/src/api.helpers.ts +0 -30
  131. package/src/api.monitor.ts +0 -106
  132. package/src/api.plugins.ts +0 -139
  133. package/src/api.vfs.ts +0 -182
  134. package/src/apiMiddleware.ts +0 -124
  135. package/src/block.ts +0 -35
  136. package/src/commands.ts +0 -122
  137. package/src/config.ts +0 -166
  138. package/src/connections.ts +0 -60
  139. package/src/const.ts +0 -57
  140. package/src/crypt.ts +0 -16
  141. package/src/debounceAsync.ts +0 -51
  142. package/src/events.ts +0 -6
  143. package/src/frontEndApis.ts +0 -17
  144. package/src/github.ts +0 -102
  145. package/src/index.ts +0 -53
  146. package/src/listen.ts +0 -220
  147. package/src/log.ts +0 -128
  148. package/src/middlewares.ts +0 -176
  149. package/src/misc.ts +0 -149
  150. package/src/pbkdf2.ts +0 -83
  151. package/src/perm.ts +0 -194
  152. package/src/plugins.ts +0 -342
  153. package/src/serveFile.ts +0 -104
  154. package/src/serveGuiFiles.ts +0 -95
  155. package/src/sse.ts +0 -29
  156. package/src/throttler.ts +0 -106
  157. package/src/update.ts +0 -67
  158. package/src/util-files.ts +0 -137
  159. package/src/util-generators.ts +0 -29
  160. package/src/util-http.ts +0 -29
  161. package/src/vfs.ts +0 -258
  162. package/src/watchLoad.ts +0 -75
  163. package/src/zip.ts +0 -69
package/src/config.js ADDED
@@ -0,0 +1,168 @@
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
3
+ var __importDefault = (this && this.__importDefault) || function (mod) {
4
+ return (mod && mod.__esModule) ? mod : { "default": mod };
5
+ };
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.saveConfigAsap = exports.setConfig = exports.getWholeConfig = exports.getConfig = exports.getConfigDefinition = exports.defineConfig = void 0;
8
+ const events_1 = __importDefault(require("events"));
9
+ const const_1 = require("./const");
10
+ const watchLoad_1 = require("./watchLoad");
11
+ const yaml_1 = __importDefault(require("yaml"));
12
+ const lodash_1 = __importDefault(require("lodash"));
13
+ const misc_1 = require("./misc");
14
+ const fs_1 = require("fs");
15
+ const path_1 = require("path");
16
+ const events_2 = __importDefault(require("./events"));
17
+ const FILE = 'config.yaml';
18
+ const configProps = {};
19
+ let started = false; // this will tell the difference for subscribeConfig()s that are called before or after config is loaded
20
+ let state = {};
21
+ const cfgEvents = new events_1.default();
22
+ cfgEvents.setMaxListeners(10000);
23
+ const path = (0, misc_1.with_)(const_1.argv.config || process.env.HFS_CONFIG, p => {
24
+ if (!p)
25
+ return FILE;
26
+ p = (0, path_1.resolve)(const_1.ORIGINAL_CWD, p);
27
+ try {
28
+ if ((0, fs_1.statSync)(p).isDirectory()) // try to detect if path points to a folder, in which case we add the standard filename
29
+ return (0, path_1.join)(p, FILE);
30
+ }
31
+ catch (_a) { }
32
+ return p;
33
+ });
34
+ console.log("config", path);
35
+ const legacyPosition = (0, path_1.join)(const_1.APP_PATH, FILE);
36
+ if (!(0, fs_1.existsSync)(path) && (0, fs_1.existsSync)(legacyPosition))
37
+ try {
38
+ (0, fs_1.renameSync)(legacyPosition, path);
39
+ console.log("moved from legacy position", legacyPosition);
40
+ }
41
+ catch (_a) {
42
+ try { // attempt copying, in case moving the source file proves to be impractical
43
+ (0, fs_1.copyFileSync)(legacyPosition, path);
44
+ console.log("copied from legacy position", legacyPosition);
45
+ }
46
+ catch (_b) { }
47
+ }
48
+ const { save } = (0, watchLoad_1.watchLoad)(path, values => setConfig(values || {}, false), {
49
+ failedOnFirstAttempt() {
50
+ console.log("No config file, using defaults");
51
+ setConfig({}, false);
52
+ }
53
+ });
54
+ function defineConfig(k, defaultValue) {
55
+ configProps[k] = { defaultValue };
56
+ return {
57
+ key() {
58
+ return k;
59
+ },
60
+ get() {
61
+ return getConfig(k);
62
+ },
63
+ sub(cb) {
64
+ return subscribeConfig(k, cb);
65
+ },
66
+ set(v) {
67
+ if (typeof v === 'function')
68
+ this.set(v(this.get()));
69
+ else
70
+ setConfig1(k, v);
71
+ }
72
+ };
73
+ }
74
+ exports.defineConfig = defineConfig;
75
+ function getConfigDefinition(k) {
76
+ return configProps[k];
77
+ }
78
+ exports.getConfigDefinition = getConfigDefinition;
79
+ const stack = [];
80
+ function subscribeConfig(k, cb) {
81
+ if (started) // initial event already passed, we'll make the first call
82
+ cb(getConfig(k));
83
+ const eventName = 'new.' + k;
84
+ return (0, misc_1.onOff)(cfgEvents, {
85
+ [eventName]() {
86
+ if (stack.includes(cb))
87
+ return; // avoid infinite loop in case a subscriber changes the value
88
+ stack.push(cb); // @ts-ignore arguments
89
+ try {
90
+ return cb.apply(this, arguments);
91
+ }
92
+ finally {
93
+ stack.pop();
94
+ }
95
+ }
96
+ });
97
+ }
98
+ function getConfig(k) {
99
+ var _a, _b;
100
+ return (_a = state[k]) !== null && _a !== void 0 ? _a : lodash_1.default.cloneDeep((_b = configProps[k]) === null || _b === void 0 ? void 0 : _b.defaultValue); // clone to avoid changing
101
+ }
102
+ exports.getConfig = getConfig;
103
+ function getWholeConfig({ omit, only }) {
104
+ const defs = (0, misc_1.objSameKeys)(configProps, x => x.defaultValue);
105
+ let copy = lodash_1.default.defaults({}, state, defs);
106
+ if (omit === null || omit === void 0 ? void 0 : omit.length)
107
+ copy = lodash_1.default.omit(copy, omit);
108
+ if (only)
109
+ copy = lodash_1.default.pick(copy, only);
110
+ return lodash_1.default.cloneDeep(copy);
111
+ }
112
+ exports.getWholeConfig = getWholeConfig;
113
+ // pass a value to `save` to force saving decision, or leave undefined for auto. Passing false will also reset previously loaded configs.
114
+ function setConfig(newCfg, save) {
115
+ if (!started) { // first time we consider also CLI args
116
+ const argCfg = lodash_1.default.pickBy((0, misc_1.objSameKeys)(configProps, (x, k) => const_1.argv[k]), x => x !== undefined);
117
+ if (!lodash_1.default.isEmpty(argCfg)) {
118
+ (0, exports.saveConfigAsap)().then(); // don't set `save` argument, as it would interfere below at check `save===false`
119
+ Object.assign(newCfg, argCfg);
120
+ }
121
+ }
122
+ for (const k in newCfg)
123
+ apply(k, newCfg[k]);
124
+ if (save) {
125
+ (0, exports.saveConfigAsap)().then();
126
+ return;
127
+ }
128
+ if (started) {
129
+ if (save === false) // false is used when loading whole config, and in such case we should not leave previous values untreated. Also, we need this only after we already `started`.
130
+ for (const k of Object.keys(state))
131
+ if (!newCfg.hasOwnProperty(k))
132
+ apply(k, newCfg[k]);
133
+ return;
134
+ }
135
+ // first time we emit also for the default values
136
+ for (const k of Object.keys(configProps))
137
+ if (!newCfg.hasOwnProperty(k))
138
+ apply(k, newCfg[k]);
139
+ started = true;
140
+ events_2.default.emit('config ready');
141
+ function apply(k, newV) {
142
+ return setConfig1(k, newV, save === undefined);
143
+ }
144
+ }
145
+ exports.setConfig = setConfig;
146
+ function setConfig1(k, newV, saveChanges = true) {
147
+ var _a;
148
+ if (lodash_1.default.isPlainObject(newV))
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))
151
+ newV = undefined;
152
+ if (started && (0, misc_1.same)(newV, state[k]))
153
+ return; // no change
154
+ const was = getConfig(k); // include cloned default, if necessary
155
+ state[k] = newV;
156
+ cfgEvents.emit('new.' + k, getConfig(k), was);
157
+ if (saveChanges)
158
+ (0, exports.saveConfigAsap)().then();
159
+ }
160
+ exports.saveConfigAsap = (0, misc_1.debounceAsync)(async () => {
161
+ while (!started)
162
+ await (0, misc_1.wait)(100);
163
+ let txt = yaml_1.default.stringify(state, { lineWidth: 1000 });
164
+ if (txt.trim() === '{}') // most users wouldn't understand
165
+ txt = '';
166
+ save(path, txt)
167
+ .catch(err => console.error('Failed at saving config file, please ensure it is writable.', String(err)));
168
+ });
@@ -0,0 +1,57 @@
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
3
+ var __importDefault = (this && this.__importDefault) || function (mod) {
4
+ return (mod && mod.__esModule) ? mod : { "default": mod };
5
+ };
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.updateConnection = exports.socket2connection = exports.getConnections = exports.newConnection = exports.normalizeIp = exports.Connection = void 0;
8
+ const events_1 = __importDefault(require("./events"));
9
+ const lodash_1 = __importDefault(require("lodash"));
10
+ class Connection {
11
+ constructor(socket) {
12
+ this.socket = socket;
13
+ this.started = new Date();
14
+ this.sent = 0;
15
+ all.push(this);
16
+ socket.on('close', () => {
17
+ all.splice(all.indexOf(this), 1);
18
+ events_1.default.emit('connectionClosed', this);
19
+ });
20
+ events_1.default.emit('connection', this);
21
+ }
22
+ get ip() {
23
+ var _a, _b;
24
+ return ((_a = this.ctx) === null || _a === void 0 ? void 0 : _a.ip) || ((_b = this._cachedIp) !== null && _b !== void 0 ? _b : (this._cachedIp = normalizeIp(this.socket.remoteAddress || '')));
25
+ }
26
+ get secure() {
27
+ return this.socket.server.cert > '';
28
+ }
29
+ }
30
+ exports.Connection = Connection;
31
+ function normalizeIp(ip) {
32
+ return ip.replace(/^::ffff:/, ''); // simplify ipv6-mapped addresses
33
+ }
34
+ exports.normalizeIp = normalizeIp;
35
+ const all = [];
36
+ function newConnection(socket) {
37
+ new Connection(socket);
38
+ }
39
+ exports.newConnection = newConnection;
40
+ function getConnections() {
41
+ return all;
42
+ }
43
+ exports.getConnections = getConnections;
44
+ function socket2connection(socket) {
45
+ return all.find(x => // socket exposed by Koa is TLSSocket which encapsulates simple Socket, and I've found no way to access it for simple comparison
46
+ x.socket.remotePort === socket.remotePort // but we can still match them because IP:PORT is key
47
+ && x.socket.remoteAddress === socket.remoteAddress);
48
+ }
49
+ exports.socket2connection = socket2connection;
50
+ function updateConnection(conn, change) {
51
+ // if no change is detected, skip update. ctx is a special case
52
+ if (!change.ctx && Object.entries(change).every(([k, v]) => lodash_1.default.isEqual(v, conn[k])))
53
+ return;
54
+ Object.assign(conn, change);
55
+ events_1.default.emit('connectionUpdated', conn, change);
56
+ }
57
+ exports.updateConnection = updateConnection;
package/src/const.js ADDED
@@ -0,0 +1,94 @@
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
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
+ };
26
+ var __importDefault = (this && this.__importDefault) || function (mod) {
27
+ return (mod && mod.__esModule) ? mod : { "default": mod };
28
+ };
29
+ Object.defineProperty(exports, "__esModule", { value: true });
30
+ exports.APP_PATH = exports.IS_WINDOWS = exports.HTTP_SERVER_ERROR = exports.HTTP_FOOL = exports.HTTP_RANGE_NOT_SATISFIABLE = 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
+ const minimist_1 = __importDefault(require("minimist"));
32
+ const fs = __importStar(require("fs"));
33
+ const os_1 = require("os");
34
+ const fs_1 = require("fs");
35
+ const path_1 = require("path");
36
+ exports.argv = (0, minimist_1.default)(process.argv.slice(2));
37
+ exports.DEV = process.env.DEV || exports.argv.dev ? 'DEV' : '';
38
+ exports.ORIGINAL_CWD = process.cwd();
39
+ exports.HFS_STARTED = new Date();
40
+ const PKG_PATH = (0, path_1.join)(__dirname, '..', 'package.json');
41
+ exports.BUILD_TIMESTAMP = fs.statSync(PKG_PATH).mtime.toISOString();
42
+ const pkg = JSON.parse(fs.readFileSync(PKG_PATH, 'utf8'));
43
+ exports.VERSION = pkg.version;
44
+ exports.DAY = 86400000;
45
+ exports.SESSION_DURATION = exports.DAY;
46
+ exports.API_VERSION = 4.1; // array.$width
47
+ exports.COMPATIBLE_API_VERSION = 1; // while changes in the api are not breaking, this number stays the same, otherwise is made equal to API_VERSION
48
+ exports.SPECIAL_URI = '/~/';
49
+ exports.FRONTEND_URI = exports.SPECIAL_URI + 'frontend/';
50
+ exports.ADMIN_URI = exports.SPECIAL_URI + 'admin/';
51
+ exports.API_URI = exports.SPECIAL_URI + 'api/';
52
+ exports.PLUGINS_PUB_URI = exports.SPECIAL_URI + 'plugins/';
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_RANGE_NOT_SATISFIABLE = 416;
66
+ exports.HTTP_FOOL = 418;
67
+ exports.HTTP_SERVER_ERROR = 500;
68
+ exports.IS_WINDOWS = process.platform === 'win32';
69
+ const IS_BINARY = !(0, path_1.basename)(process.argv0).includes('node'); // this won't be node if pkg was used
70
+ exports.APP_PATH = (0, path_1.dirname)(IS_BINARY ? process.argv0 : __dirname);
71
+ // we want this to be the first stuff to be printed, then we print it in this module, that is executed at the beginning
72
+ if (exports.DEV)
73
+ console.clear();
74
+ else
75
+ console.debug = () => { };
76
+ console.log(`HFS ~ HTTP File Server - Copyright 2021-2023, Massimo Melina <a@rejetto.com>`);
77
+ console.log(`License https://www.gnu.org/licenses/gpl-3.0.txt`);
78
+ console.log('started', exports.HFS_STARTED.toLocaleString(), exports.DEV);
79
+ console.log('version', exports.VERSION || '-');
80
+ console.log('build', exports.BUILD_TIMESTAMP || '-');
81
+ if (exports.argv.cwd)
82
+ process.chdir(exports.argv.cwd);
83
+ else if (!process.argv0.endsWith('.exe')) { // still considering whether to use this behavior with Windows users, who may be less accustomed to it
84
+ const dir = (0, path_1.join)((0, os_1.homedir)(), '.hfs');
85
+ try {
86
+ (0, fs_1.mkdirSync)(dir);
87
+ }
88
+ catch (e) {
89
+ if (e.code !== 'EEXIST')
90
+ console.error(e);
91
+ }
92
+ process.chdir(dir);
93
+ }
94
+ console.log('cwd', process.cwd());
package/src/crypt.js ADDED
@@ -0,0 +1,21 @@
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
3
+ var __importDefault = (this && this.__importDefault) || function (mod) {
4
+ return (mod && mod.__esModule) ? mod : { "default": mod };
5
+ };
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.verifyPassword = exports.hashPassword = void 0;
8
+ // simple wrapper
9
+ // @ts-ignore
10
+ const pbkdf2_1 = require("./pbkdf2");
11
+ const assert_1 = __importDefault(require("assert"));
12
+ async function hashPassword(s) {
13
+ return 'p2:' + await (0, pbkdf2_1.pbkdf2)(s);
14
+ }
15
+ exports.hashPassword = hashPassword;
16
+ async function verifyPassword(hashed, given) {
17
+ const i = hashed.indexOf(':');
18
+ (0, assert_1.default)(i > 0, 'bad hashed');
19
+ return await (0, pbkdf2_1.pbkdf2Verify)(hashed.slice(i + 1), given); // for the time being we totally ignore the "method" part
20
+ }
21
+ exports.verifyPassword = verifyPassword;
@@ -0,0 +1,49 @@
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
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ // like lodash.debounce, but also avoids async invocations to overlap
5
+ function debounceAsync(callback, wait = 100, { leading = false, maxWait = Infinity } = {}) {
6
+ let started = 0; // latest callback invocation
7
+ let runningCallback; // latest callback invocation result
8
+ let runningDebouncer; // latest wrapper invocation
9
+ let waitingSince = 0; // we are delaying invocation since
10
+ let whoIsWaiting; // args' array object identifies the pending instance, and incidentally stores args
11
+ const interceptingWrapper = (...args) => runningDebouncer = debouncer.apply(null, args);
12
+ return Object.assign(interceptingWrapper, {
13
+ cancel: () => {
14
+ waitingSince = 0;
15
+ whoIsWaiting = undefined;
16
+ },
17
+ flush: () => runningCallback !== null && runningCallback !== void 0 ? runningCallback : exec(),
18
+ });
19
+ async function debouncer(...args) {
20
+ if (runningCallback)
21
+ return await runningCallback;
22
+ whoIsWaiting = args;
23
+ waitingSince || (waitingSince = Date.now());
24
+ const waitingCap = maxWait - (Date.now() - (waitingSince || started));
25
+ const waitFor = Math.min(waitingCap, leading ? wait - (Date.now() - started) : wait);
26
+ if (waitFor > 0)
27
+ await new Promise(resolve => setTimeout(resolve, waitFor));
28
+ if (!whoIsWaiting) // canceled
29
+ return void (waitingSince = 0);
30
+ if (whoIsWaiting !== args) // another fresher call is waiting
31
+ return runningDebouncer;
32
+ return await exec();
33
+ }
34
+ async function exec() {
35
+ if (!whoIsWaiting)
36
+ return;
37
+ waitingSince = 0;
38
+ started = Date.now();
39
+ try {
40
+ runningCallback = callback.apply(null, whoIsWaiting);
41
+ return await runningCallback;
42
+ }
43
+ finally {
44
+ whoIsWaiting = undefined;
45
+ runningCallback = undefined;
46
+ }
47
+ }
48
+ }
49
+ exports.default = debounceAsync;
package/src/events.js ADDED
@@ -0,0 +1,9 @@
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
3
+ var __importDefault = (this && this.__importDefault) || function (mod) {
4
+ return (mod && mod.__esModule) ? mod : { "default": mod };
5
+ };
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ const events_1 = __importDefault(require("events"));
8
+ // app-wide events
9
+ exports.default = new events_1.default();
@@ -0,0 +1,38 @@
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
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
+ };
26
+ Object.defineProperty(exports, "__esModule", { value: true });
27
+ exports.frontEndApis = void 0;
28
+ const api_file_list_1 = require("./api.file_list");
29
+ const api_auth = __importStar(require("./api.auth"));
30
+ const config_1 = require("./config");
31
+ const customHeader = (0, config_1.defineConfig)('custom_header');
32
+ exports.frontEndApis = {
33
+ file_list: api_file_list_1.file_list,
34
+ ...api_auth,
35
+ config() {
36
+ return Object.fromEntries([customHeader].map(x => [x.key(), x.get()]));
37
+ }
38
+ };
package/src/github.js ADDED
@@ -0,0 +1,104 @@
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
3
+ var __importDefault = (this && this.__importDefault) || function (mod) {
4
+ return (mod && mod.__esModule) ? mod : { "default": mod };
5
+ };
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.searchPlugins = exports.getFolder2repo = exports.readOnlinePlugin = exports.getRepoInfo = exports.downloadPlugin = void 0;
8
+ const events_1 = __importDefault(require("./events"));
9
+ const misc_1 = require("./misc");
10
+ const plugins_1 = require("./plugins");
11
+ const apiMiddleware_1 = require("./apiMiddleware");
12
+ const lodash_1 = __importDefault(require("lodash"));
13
+ const const_1 = require("./const");
14
+ const DIST_ROOT = 'dist/';
15
+ const downloading = {};
16
+ function downloadProgress(id, status) {
17
+ if (status === undefined)
18
+ delete downloading[id];
19
+ else
20
+ downloading[id] = status;
21
+ events_1.default.emit('pluginDownload_' + id, status);
22
+ }
23
+ async function downloadPlugin(repo, branch = '', overwrite) {
24
+ if (downloading[repo])
25
+ return new apiMiddleware_1.ApiError(const_1.HTTP_CONFLICT, "already downloading");
26
+ downloadProgress(repo, true);
27
+ const rec = await getRepoInfo(repo);
28
+ if (!branch)
29
+ branch = rec.default_branch;
30
+ const short = repo.split('/')[1]; // second part, repo without the owner
31
+ const folder2repo = getFolder2repo();
32
+ const folder = overwrite ? lodash_1.default.findKey(folder2repo, x => x === repo) // use existing folder
33
+ : short in folder2repo ? repo.replace('/', '-') // longer form only if another plugin is using short form
34
+ : short;
35
+ const installPath = plugins_1.PATH + '/' + folder;
36
+ const GITHUB_ZIP_ROOT = short + '-' + branch; // GitHub puts everything within this folder
37
+ const rootWithinZip = GITHUB_ZIP_ROOT + '/' + DIST_ROOT;
38
+ const stream = await (0, misc_1.httpsStream)(`https://github.com/${repo}/archive/refs/heads/${branch}.zip`);
39
+ await (0, misc_1.unzip)(stream, path => path.startsWith(rootWithinZip) && installPath + '/' + path.slice(rootWithinZip.length));
40
+ downloadProgress(repo, undefined);
41
+ await (0, plugins_1.rescan)(); // workaround: for some reason, operations are not triggering the rescan of the watched folder. Let's invoke it.
42
+ return folder;
43
+ }
44
+ exports.downloadPlugin = downloadPlugin;
45
+ function getRepoInfo(id) {
46
+ return apiGithub('repos/' + id);
47
+ }
48
+ exports.getRepoInfo = getRepoInfo;
49
+ async function readOnlinePlugin(repoInfo, branch = '') {
50
+ const url = `https://raw.githubusercontent.com/${repoInfo.full_name}/${branch || repoInfo.default_branch}/${DIST_ROOT}plugin.js`;
51
+ const res = await (0, misc_1.httpsString)(url);
52
+ if (!res.ok)
53
+ throw res.statusCode;
54
+ const pl = (0, plugins_1.parsePluginSource)(repoInfo.full_name, res.body); // use 'repo' as 'id' client-side
55
+ pl.branch = branch || undefined;
56
+ return pl;
57
+ }
58
+ exports.readOnlinePlugin = readOnlinePlugin;
59
+ function getFolder2repo() {
60
+ const ret = Object.fromEntries((0, plugins_1.getAvailablePlugins)().map(x => [x.id, x.repo]));
61
+ Object.assign(ret, Object.fromEntries((0, plugins_1.mapPlugins)(x => [x.id, x.getData().repo])));
62
+ return ret;
63
+ }
64
+ exports.getFolder2repo = getFolder2repo;
65
+ async function apiGithub(uri) {
66
+ const res = await (0, misc_1.httpsString)('https://api.github.com/' + uri, {
67
+ headers: {
68
+ 'User-Agent': 'HFS',
69
+ Accept: 'application/vnd.github.v3+json',
70
+ }
71
+ });
72
+ if (!res.ok)
73
+ throw res.statusCode;
74
+ return JSON.parse(res.body);
75
+ }
76
+ async function* searchPlugins(text) {
77
+ var _a;
78
+ const res = await apiGithub('search/repositories?q=topic:hfs-plugin+' + encodeURI(text));
79
+ for (const it of res.items) {
80
+ const repo = it.full_name;
81
+ let pl = await readOnlinePlugin(it);
82
+ if (!pl.apiRequired)
83
+ continue; // mandatory field
84
+ if (pl.badApi) { // we try other branches (starting with 'api')
85
+ const branches = (await apiGithub('repos/' + it.full_name + '/branches'))
86
+ .map((x) => x.name).filter((x) => x.startsWith('api')).sort();
87
+ for (const branch of branches) {
88
+ pl = await readOnlinePlugin(it, branch);
89
+ if (!pl.apiRequired)
90
+ pl.badApi = '-';
91
+ if (!pl.badApi)
92
+ break;
93
+ }
94
+ }
95
+ if (pl.badApi)
96
+ continue;
97
+ Object.assign(pl, {
98
+ downloading: downloading[repo],
99
+ license: (_a = it.license) === null || _a === void 0 ? void 0 : _a.spdx_id,
100
+ }, lodash_1.default.pick(it, ['pushed_at', 'stargazers_count']));
101
+ yield pl;
102
+ }
103
+ }
104
+ exports.searchPlugins = searchPlugins;
package/src/index.js ADDED
@@ -0,0 +1,57 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
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
+ var __importDefault = (this && this.__importDefault) || function (mod) {
5
+ return (mod && mod.__esModule) ? mod : { "default": mod };
6
+ };
7
+ var _a;
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.app = void 0;
10
+ const koa_1 = __importDefault(require("koa"));
11
+ const koa_mount_1 = __importDefault(require("koa-mount"));
12
+ const apiMiddleware_1 = require("./apiMiddleware");
13
+ const const_1 = require("./const");
14
+ const frontEndApis_1 = require("./frontEndApis");
15
+ const log_1 = require("./log");
16
+ const plugins_1 = require("./plugins");
17
+ const throttler_1 = require("./throttler");
18
+ const middlewares_1 = require("./middlewares");
19
+ require("./listen");
20
+ require("./commands");
21
+ const adminApis_1 = require("./adminApis");
22
+ const config_1 = require("./config");
23
+ const assert_1 = require("assert");
24
+ const lodash_1 = __importDefault(require("lodash"));
25
+ const misc_1 = require("./misc");
26
+ (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
+ const keys = ((_a = process.env.COOKIE_SIGN_KEYS) === null || _a === void 0 ? void 0 : _a.split(',')) || [(0, misc_1.randomId)(30)];
28
+ exports.app = new koa_1.default({ keys });
29
+ exports.app.use(middlewares_1.someSecurity)
30
+ .use((0, middlewares_1.sessions)(exports.app))
31
+ .use(middlewares_1.prepareState)
32
+ .use(middlewares_1.headRequests)
33
+ .use((0, log_1.log)())
34
+ .use(throttler_1.throttler)
35
+ .use(middlewares_1.gzipper)
36
+ .use(middlewares_1.paramsDecoder)
37
+ .use((0, plugins_1.pluginsMiddleware)())
38
+ .use((0, koa_mount_1.default)(const_1.API_URI, (0, apiMiddleware_1.apiMiddleware)({ ...frontEndApis_1.frontEndApis, ...adminApis_1.adminApis })))
39
+ .use(middlewares_1.serveGuiAndSharedFiles)
40
+ .on('error', errorHandler);
41
+ function errorHandler(err) {
42
+ const { code } = err;
43
+ if (const_1.DEV && code === 'ENOENT' && err.path.endsWith('sockjs-node'))
44
+ return; // spam out dev stuff
45
+ if (code === 'ECANCELED' || code === 'ECONNRESET' || code === 'ECONNABORTED' || code === 'EPIPE'
46
+ || code === 'HPE_INVALID_EOF_STATE')
47
+ return; // someone interrupted, don't care
48
+ console.error('server error', err);
49
+ }
50
+ process.on('uncaughtException', err => {
51
+ if (err.syscall !== 'watch')
52
+ console.error(err);
53
+ });
54
+ (0, config_1.defineConfig)('proxies', 0).sub(n => {
55
+ exports.app.proxy = n > 0;
56
+ exports.app.maxIpsCount = n;
57
+ });