hfs 0.26.8 → 0.26.9

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 (161) hide show
  1. package/admin/assets/index.bb5198ec.js +281 -0
  2. package/admin/assets/index.dcc78777.css +1 -0
  3. package/admin/assets/sha512.9dfe82e1.js +8 -0
  4. package/admin/index.html +3 -1
  5. package/admin/{public/logo.svg → logo.svg} +0 -0
  6. package/frontend/assets/index.27a78796.js +85 -0
  7. package/frontend/assets/index.93366732.css +1 -0
  8. package/frontend/assets/sha512.6af42937.js +8 -0
  9. package/frontend/{public/fontello.css → fontello.css} +0 -0
  10. package/frontend/{public/fontello.woff2 → fontello.woff2} +0 -0
  11. package/frontend/index.html +3 -1
  12. package/package.json +1 -1
  13. package/src/QuickZipStream.js +285 -0
  14. package/src/ThrottledStream.js +93 -0
  15. package/src/adminApis.js +169 -0
  16. package/src/api.accounts.js +59 -0
  17. package/src/api.auth.js +128 -0
  18. package/src/api.file_list.js +103 -0
  19. package/src/api.helpers.js +32 -0
  20. package/src/api.monitor.js +102 -0
  21. package/src/api.plugins.js +127 -0
  22. package/src/api.vfs.js +164 -0
  23. package/src/apiMiddleware.js +120 -0
  24. package/src/block.js +33 -0
  25. package/src/commands.js +124 -0
  26. package/src/config.js +168 -0
  27. package/src/connections.js +57 -0
  28. package/src/const.js +83 -0
  29. package/src/crypt.js +21 -0
  30. package/src/debounceAsync.js +48 -0
  31. package/src/events.js +9 -0
  32. package/src/frontEndApis.js +38 -0
  33. package/src/github.js +102 -0
  34. package/src/index.js +56 -0
  35. package/src/listen.js +235 -0
  36. package/src/log.js +137 -0
  37. package/src/middlewares.js +175 -0
  38. package/src/misc.js +160 -0
  39. package/src/pbkdf2.js +74 -0
  40. package/src/perm.js +181 -0
  41. package/src/plugins.js +343 -0
  42. package/src/serveFile.js +105 -0
  43. package/src/serveGuiFiles.js +113 -0
  44. package/src/sse.js +29 -0
  45. package/src/throttler.js +91 -0
  46. package/src/update.js +69 -0
  47. package/src/util-files.js +148 -0
  48. package/src/util-generators.js +30 -0
  49. package/src/util-http.js +30 -0
  50. package/src/vfs.js +230 -0
  51. package/src/watchLoad.js +73 -0
  52. package/src/zip.js +72 -0
  53. package/admin/.DS_Store +0 -0
  54. package/admin/.eslintrc +0 -8
  55. package/admin/.gitignore +0 -23
  56. package/admin/package.json +0 -67
  57. package/admin/src/AccountForm.ts +0 -92
  58. package/admin/src/AccountsPage.ts +0 -143
  59. package/admin/src/App.ts +0 -83
  60. package/admin/src/ArrayField.ts +0 -84
  61. package/admin/src/ConfigPage.ts +0 -279
  62. package/admin/src/FileField.ts +0 -52
  63. package/admin/src/FileForm.ts +0 -148
  64. package/admin/src/FilePicker.ts +0 -166
  65. package/admin/src/HomePage.ts +0 -96
  66. package/admin/src/InstalledPlugins.ts +0 -158
  67. package/admin/src/LoginRequired.ts +0 -75
  68. package/admin/src/LogoutPage.ts +0 -27
  69. package/admin/src/LogsPage.ts +0 -75
  70. package/admin/src/MainMenu.ts +0 -74
  71. package/admin/src/MenuButton.ts +0 -38
  72. package/admin/src/MonitorPage.ts +0 -200
  73. package/admin/src/OnlinePlugins.ts +0 -101
  74. package/admin/src/PermField.ts +0 -80
  75. package/admin/src/PluginsPage.ts +0 -27
  76. package/admin/src/VfsMenuBar.ts +0 -58
  77. package/admin/src/VfsPage.ts +0 -124
  78. package/admin/src/VfsTree.ts +0 -95
  79. package/admin/src/addFiles.ts +0 -59
  80. package/admin/src/api.ts +0 -246
  81. package/admin/src/dialog.ts +0 -203
  82. package/admin/src/index.css +0 -21
  83. package/admin/src/index.ts +0 -10
  84. package/admin/src/md.ts +0 -31
  85. package/admin/src/misc.ts +0 -141
  86. package/admin/src/react-app-env.d.ts +0 -1
  87. package/admin/src/reportWebVitals.ts +0 -15
  88. package/admin/src/setupTests.ts +0 -5
  89. package/admin/src/state.ts +0 -40
  90. package/admin/src/theme.ts +0 -37
  91. package/admin/tsconfig.json +0 -26
  92. package/admin/vite.config.ts +0 -32
  93. package/frontend/.DS_Store +0 -0
  94. package/frontend/.eslintrc +0 -8
  95. package/frontend/.gitignore +0 -23
  96. package/frontend/package.json +0 -51
  97. package/frontend/src/App.ts +0 -25
  98. package/frontend/src/Breadcrumbs.ts +0 -43
  99. package/frontend/src/BrowseFiles.ts +0 -141
  100. package/frontend/src/Head.ts +0 -45
  101. package/frontend/src/UserPanel.ts +0 -52
  102. package/frontend/src/api.ts +0 -78
  103. package/frontend/src/components.ts +0 -54
  104. package/frontend/src/dialog.css +0 -76
  105. package/frontend/src/dialog.ts +0 -105
  106. package/frontend/src/icons.ts +0 -46
  107. package/frontend/src/index.scss +0 -307
  108. package/frontend/src/index.ts +0 -10
  109. package/frontend/src/login.ts +0 -50
  110. package/frontend/src/menu.ts +0 -188
  111. package/frontend/src/misc.ts +0 -54
  112. package/frontend/src/options.ts +0 -52
  113. package/frontend/src/react-app-env.d.ts +0 -1
  114. package/frontend/src/reportWebVitals.ts +0 -15
  115. package/frontend/src/setupTests.ts +0 -5
  116. package/frontend/src/state.ts +0 -82
  117. package/frontend/src/useAuthorized.ts +0 -17
  118. package/frontend/src/useFetchList.ts +0 -144
  119. package/frontend/src/useTheme.ts +0 -23
  120. package/frontend/tsconfig.json +0 -26
  121. package/frontend/vite.config.ts +0 -21
  122. package/src/QuickZipStream.ts +0 -279
  123. package/src/ThrottledStream.ts +0 -98
  124. package/src/adminApis.ts +0 -161
  125. package/src/api.accounts.ts +0 -78
  126. package/src/api.auth.ts +0 -131
  127. package/src/api.file_list.ts +0 -102
  128. package/src/api.helpers.ts +0 -30
  129. package/src/api.monitor.ts +0 -106
  130. package/src/api.plugins.ts +0 -139
  131. package/src/api.vfs.ts +0 -182
  132. package/src/apiMiddleware.ts +0 -124
  133. package/src/block.ts +0 -35
  134. package/src/commands.ts +0 -122
  135. package/src/config.ts +0 -166
  136. package/src/connections.ts +0 -60
  137. package/src/const.ts +0 -57
  138. package/src/crypt.ts +0 -16
  139. package/src/debounceAsync.ts +0 -51
  140. package/src/events.ts +0 -6
  141. package/src/frontEndApis.ts +0 -17
  142. package/src/github.ts +0 -102
  143. package/src/index.ts +0 -53
  144. package/src/listen.ts +0 -220
  145. package/src/log.ts +0 -128
  146. package/src/middlewares.ts +0 -176
  147. package/src/misc.ts +0 -149
  148. package/src/pbkdf2.ts +0 -83
  149. package/src/perm.ts +0 -194
  150. package/src/plugins.ts +0 -342
  151. package/src/serveFile.ts +0 -104
  152. package/src/serveGuiFiles.ts +0 -95
  153. package/src/sse.ts +0 -29
  154. package/src/throttler.ts +0 -106
  155. package/src/update.ts +0 -67
  156. package/src/util-files.ts +0 -137
  157. package/src/util-generators.ts +0 -29
  158. package/src/util-http.ts +0 -29
  159. package/src/vfs.ts +0 -258
  160. package/src/watchLoad.ts +0 -75
  161. package/src/zip.ts +0 -69
package/src/perm.js ADDED
@@ -0,0 +1,181 @@
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 __importDefault = (this && this.__importDefault) || function (mod) {
4
+ return (mod && mod.__esModule) ? mod : { "default": mod };
5
+ };
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;
8
+ const lodash_1 = __importDefault(require("lodash"));
9
+ const crypt_1 = require("./crypt");
10
+ const misc_1 = require("./misc");
11
+ const config_1 = require("./config");
12
+ const tssrp6a_1 = require("tssrp6a");
13
+ const events_1 = __importDefault(require("./events"));
14
+ let accounts = {};
15
+ function getAccounts() {
16
+ return accounts;
17
+ }
18
+ exports.getAccounts = getAccounts;
19
+ function getCurrentUsername(ctx) {
20
+ var _a;
21
+ return ((_a = ctx.state.account) === null || _a === void 0 ? void 0 : _a.username) || '';
22
+ }
23
+ exports.getCurrentUsername = getCurrentUsername;
24
+ // provides the username and all other usernames it inherits based on the 'belongs' attribute. Useful to check permissions
25
+ function getCurrentUsernameExpanded(ctx) {
26
+ const who = getCurrentUsername(ctx);
27
+ if (!who)
28
+ return [];
29
+ const ret = [who];
30
+ for (const u of ret) {
31
+ const a = getAccount(u);
32
+ if (a === null || a === void 0 ? void 0 : a.belongs)
33
+ ret.push(...a.belongs);
34
+ }
35
+ return ret;
36
+ }
37
+ exports.getCurrentUsernameExpanded = getCurrentUsernameExpanded;
38
+ function getAccount(username, normalize = true) {
39
+ if (normalize)
40
+ username = normalizeUsername(username);
41
+ return username ? accounts[username] : undefined;
42
+ }
43
+ exports.getAccount = getAccount;
44
+ function saveSrpInfo(account, salt, verifier) {
45
+ account.srp = String(salt) + '|' + String(verifier);
46
+ }
47
+ exports.saveSrpInfo = saveSrpInfo;
48
+ exports.allowClearTextLogin = (0, config_1.defineConfig)('allow_clear_text_login');
49
+ const srp6aNimbusRoutines = new tssrp6a_1.SRPRoutines(new tssrp6a_1.SRPParameters());
50
+ async function updateAccount(account, changer) {
51
+ const was = JSON.stringify(account);
52
+ await (changer === null || changer === void 0 ? void 0 : changer(account));
53
+ const { username } = account;
54
+ if (account.password) {
55
+ console.debug('hashing password for', username);
56
+ if (exports.allowClearTextLogin.get())
57
+ account.hashed_password = await (0, crypt_1.hashPassword)(account.password);
58
+ const res = await (0, tssrp6a_1.createVerifierAndSalt)(srp6aNimbusRoutines, username, account.password);
59
+ saveSrpInfo(account, res.s, res.v);
60
+ delete account.password;
61
+ }
62
+ else if (!account.srp && account.hashed_password) {
63
+ console.log('please reset password for account', username);
64
+ process.exit(1);
65
+ }
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}`));
69
+ if (was !== JSON.stringify(account))
70
+ saveAccountsAsap();
71
+ }
72
+ 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) => {
79
+ 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
+ }));
87
+ });
88
+ function normalizeUsername(username) {
89
+ return username.toLocaleLowerCase();
90
+ }
91
+ exports.normalizeUsername = normalizeUsername;
92
+ function renameAccount(from, to) {
93
+ from = normalizeUsername(from);
94
+ to = normalizeUsername(to);
95
+ if (!to || !accounts[from] || accounts[to])
96
+ return false;
97
+ if (to === from)
98
+ return true;
99
+ (0, misc_1.objRenameKey)(accounts, from, to);
100
+ updateReferences();
101
+ saveAccountsAsap();
102
+ return true;
103
+ function updateReferences() {
104
+ var _a;
105
+ (0, misc_1.setHidden)(accounts[to], { username: to });
106
+ for (const a of Object.values(accounts)) {
107
+ const idx = (_a = a.belongs) === null || _a === void 0 ? void 0 : _a.indexOf(from);
108
+ if (idx !== undefined && idx >= 0)
109
+ a.belongs[idx] = to;
110
+ }
111
+ events_1.default.emit('accountRenamed', from, to); // everybody, take care of your stuff
112
+ }
113
+ }
114
+ exports.renameAccount = renameAccount;
115
+ // we consider all the following fields, when falsy, as equivalent to be missing. If this changes in the future, please adjust addAccount and setAccount
116
+ const assignableProps = ['redirect', 'ignore_limits', 'belongs', 'admin'];
117
+ function addAccount(username, props) {
118
+ username = normalizeUsername(username);
119
+ if (!username || getAccount(username, false))
120
+ return;
121
+ const filteredProps = lodash_1.default.pickBy(lodash_1.default.pick(props, assignableProps), Boolean);
122
+ 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();
125
+ return copy;
126
+ }
127
+ exports.addAccount = addAccount;
128
+ function setAccount(username, changes) {
129
+ const acc = getAccount(username);
130
+ if (!acc)
131
+ return false;
132
+ const rest = lodash_1.default.pick(changes, assignableProps);
133
+ for (const [k, v] of Object.entries(rest))
134
+ if (!v)
135
+ rest[k] = undefined;
136
+ Object.assign(acc, rest);
137
+ if (changes.username)
138
+ renameAccount(username, changes.username);
139
+ saveAccountsAsap().then();
140
+ return acc;
141
+ }
142
+ exports.setAccount = setAccount;
143
+ function delAccount(username) {
144
+ if (!getAccount(username))
145
+ return false;
146
+ accountsConfig.set(accounts => Object.assign(accounts, { [normalizeUsername(username)]: undefined }));
147
+ saveAccountsAsap().then();
148
+ return true;
149
+ }
150
+ exports.delAccount = delAccount;
151
+ // get some property from account, searching in its groups if necessary. Search is breadth-first, and this determines priority of inheritance.
152
+ function getFromAccount(account, getter) {
153
+ const search = [account];
154
+ for (const accountOrUsername of search) {
155
+ const a = typeof accountOrUsername === 'string' ? getAccount(accountOrUsername) : accountOrUsername;
156
+ if (!a)
157
+ continue;
158
+ const res = getter(a);
159
+ if (res !== undefined)
160
+ return res;
161
+ if (a.belongs)
162
+ search.push(...a.belongs);
163
+ }
164
+ }
165
+ exports.getFromAccount = getFromAccount;
166
+ function accountHasPassword(account) {
167
+ return Boolean(account.password || account.hashed_password || account.srp);
168
+ }
169
+ exports.accountHasPassword = accountHasPassword;
170
+ function accountCanLogin(account) {
171
+ return accountHasPassword(account);
172
+ }
173
+ exports.accountCanLogin = accountCanLogin;
174
+ function accountCanLoginAdmin(account) {
175
+ return accountCanLogin(account) && getFromAccount(account, a => a.admin);
176
+ }
177
+ exports.accountCanLoginAdmin = accountCanLoginAdmin;
178
+ function anyAccountCanLoginAdmin() {
179
+ return Object.values(accounts).find(accountCanLoginAdmin);
180
+ }
181
+ exports.anyAccountCanLoginAdmin = anyAccountCanLoginAdmin;
package/src/plugins.js ADDED
@@ -0,0 +1,343 @@
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
+ };
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.parsePluginSource = exports.rescan = exports.pluginsConfig = exports.enablePlugins = exports.pluginsWatcher = exports.getAvailablePlugins = exports.Plugin = exports.pluginsMiddleware = exports.getPluginConfigFields = exports.mapPlugins = exports.getPluginInfo = exports.setPluginConfig = exports.enablePlugin = exports.isPluginRunning = exports.DISABLING_POSTFIX = exports.PATH = void 0;
31
+ const fast_glob_1 = __importDefault(require("fast-glob"));
32
+ const watchLoad_1 = require("./watchLoad");
33
+ const lodash_1 = __importDefault(require("lodash"));
34
+ const path_1 = __importDefault(require("path"));
35
+ const const_1 = require("./const");
36
+ const Const = __importStar(require("./const"));
37
+ const misc_1 = require("./misc");
38
+ const config_1 = require("./config");
39
+ const serveFile_1 = require("./serveFile");
40
+ const events_1 = __importDefault(require("./events"));
41
+ const promises_1 = require("fs/promises");
42
+ const fs_1 = require("fs");
43
+ const connections_1 = require("./connections");
44
+ exports.PATH = 'plugins';
45
+ exports.DISABLING_POSTFIX = '-disabled';
46
+ const plugins = {};
47
+ function isPluginRunning(id) {
48
+ var _a;
49
+ return (_a = plugins[id]) === null || _a === void 0 ? void 0 : _a.started;
50
+ }
51
+ exports.isPluginRunning = isPluginRunning;
52
+ function enablePlugin(id, state = true) {
53
+ exports.enablePlugins.set(arr => arr.includes(id) === state ? arr
54
+ : state ? [...arr, id]
55
+ : arr.filter((x) => x !== id));
56
+ }
57
+ exports.enablePlugin = enablePlugin;
58
+ // nullish values are equivalent to defaultValues
59
+ function setPluginConfig(id, changes) {
60
+ exports.pluginsConfig.set(allConfigs => {
61
+ const fields = getPluginConfigFields(id);
62
+ const oldConfig = allConfigs[id];
63
+ const newConfig = lodash_1.default.pickBy({ ...oldConfig, ...changes }, (v, k) => { var _a; return v != null && !(0, misc_1.same)(v, (_a = fields === null || fields === void 0 ? void 0 : fields[k]) === null || _a === void 0 ? void 0 : _a.defaultValue); });
64
+ return { ...allConfigs, [id]: lodash_1.default.isEmpty(newConfig) ? undefined : newConfig };
65
+ });
66
+ }
67
+ exports.setPluginConfig = setPluginConfig;
68
+ function getPluginInfo(id) {
69
+ var _a;
70
+ const running = (_a = plugins[id]) === null || _a === void 0 ? void 0 : _a.getData();
71
+ return running && Object.assign(running, { id }) || availablePlugins[id];
72
+ }
73
+ exports.getPluginInfo = getPluginInfo;
74
+ function mapPlugins(cb) {
75
+ return lodash_1.default.map(plugins, (pl, plName) => {
76
+ try {
77
+ return cb(pl, plName);
78
+ }
79
+ catch (e) {
80
+ console.log('plugin error', plName, String(e));
81
+ }
82
+ }).filter(x => x !== undefined);
83
+ }
84
+ exports.mapPlugins = mapPlugins;
85
+ function getPluginConfigFields(id) {
86
+ var _a;
87
+ return (_a = plugins[id]) === null || _a === void 0 ? void 0 : _a.getData().config;
88
+ }
89
+ exports.getPluginConfigFields = getPluginConfigFields;
90
+ function pluginsMiddleware() {
91
+ return async (ctx, next) => {
92
+ var _a;
93
+ const after = [];
94
+ // run middleware plugins
95
+ for (const id in plugins)
96
+ try {
97
+ const pl = plugins[id];
98
+ const res = await ((_a = pl.middleware) === null || _a === void 0 ? void 0 : _a.call(pl, ctx));
99
+ if (res === true)
100
+ ctx.pluginStopped = true;
101
+ if (typeof res === 'function')
102
+ after.push(res);
103
+ }
104
+ catch (e) {
105
+ console.log('error middleware plugin', id, String(e));
106
+ console.debug(e);
107
+ }
108
+ // expose public plugins' files
109
+ const { path } = ctx;
110
+ if (!ctx.pluginStopped) {
111
+ if (path.startsWith(const_1.PLUGINS_PUB_URI)) {
112
+ const a = path.substring(const_1.PLUGINS_PUB_URI.length).split('/');
113
+ if (plugins.hasOwnProperty(a[0])) { // do it only if the plugin is loaded
114
+ a.splice(1, 0, 'public');
115
+ await (0, serveFile_1.serveFile)(exports.PATH + '/' + a.join('/'), 'auto')(ctx, next);
116
+ }
117
+ }
118
+ await next();
119
+ }
120
+ for (const f of after)
121
+ await f();
122
+ };
123
+ }
124
+ exports.pluginsMiddleware = pluginsMiddleware;
125
+ class Plugin {
126
+ constructor(id, data, unwatch) {
127
+ this.id = id;
128
+ this.data = data;
129
+ this.unwatch = unwatch;
130
+ this.started = new Date();
131
+ if (!data)
132
+ throw 'invalid data';
133
+ if (plugins[id])
134
+ throw "unload first: " + id;
135
+ plugins[id] = this;
136
+ this.data = data = { ...data }; // clone to make object modifiable. Objects coming from import are not.
137
+ // some validation
138
+ for (const k of ['frontend_css', 'frontend_js']) {
139
+ const v = data[k];
140
+ if (typeof v === 'string')
141
+ data[k] = [v];
142
+ else if (v && !Array.isArray(v)) {
143
+ delete data[k];
144
+ console.warn('invalid', k);
145
+ }
146
+ }
147
+ }
148
+ get middleware() {
149
+ var _a;
150
+ return (_a = this.data) === null || _a === void 0 ? void 0 : _a.middleware;
151
+ }
152
+ get frontend_css() {
153
+ var _a;
154
+ return (_a = this.data) === null || _a === void 0 ? void 0 : _a.frontend_css;
155
+ }
156
+ get frontend_js() {
157
+ var _a;
158
+ return (_a = this.data) === null || _a === void 0 ? void 0 : _a.frontend_js;
159
+ }
160
+ get onDirEntry() {
161
+ var _a;
162
+ return (_a = this.data) === null || _a === void 0 ? void 0 : _a.onDirEntry;
163
+ }
164
+ getData() {
165
+ return { ...this.data };
166
+ }
167
+ async unload(reloading = false) {
168
+ var _a, _b;
169
+ const { id } = this;
170
+ try {
171
+ await ((_b = (_a = this.data) === null || _a === void 0 ? void 0 : _a.unload) === null || _b === void 0 ? void 0 : _b.call(_a));
172
+ console.log('unloaded plugin', id);
173
+ }
174
+ catch (e) {
175
+ console.log('error unloading plugin', id, String(e));
176
+ }
177
+ delete plugins[id];
178
+ if (reloading)
179
+ return;
180
+ this.unwatch();
181
+ if (availablePlugins[id])
182
+ events_1.default.emit('pluginStopped', availablePlugins[id]);
183
+ else
184
+ events_1.default.emit('pluginUninstalled', id);
185
+ }
186
+ }
187
+ exports.Plugin = Plugin;
188
+ let availablePlugins = {};
189
+ function getAvailablePlugins() {
190
+ return Object.values(availablePlugins);
191
+ }
192
+ exports.getAvailablePlugins = getAvailablePlugins;
193
+ const rescanAsap = (0, misc_1.debounceAsync)(rescan, 1000);
194
+ if (!(0, fs_1.existsSync)(exports.PATH))
195
+ try {
196
+ (0, fs_1.mkdirSync)(exports.PATH);
197
+ }
198
+ catch (_a) { }
199
+ exports.pluginsWatcher = (0, misc_1.watchDir)(exports.PATH, rescanAsap);
200
+ exports.enablePlugins = (0, config_1.defineConfig)('enable_plugins', ['antibrute']);
201
+ exports.enablePlugins.sub(rescanAsap);
202
+ exports.pluginsConfig = (0, config_1.defineConfig)('plugins_config', {});
203
+ async function rescan() {
204
+ console.debug('scanning plugins');
205
+ const found = [];
206
+ const foundDisabled = {};
207
+ const MASK = exports.PATH + '/*/plugin.js'; // be sure to not use path.join as fast-glob doesn't work with \
208
+ for (const f of await (0, fast_glob_1.default)([(0, misc_1.adjustStaticPathForGlob)(const_1.APP_PATH) + '/' + MASK, MASK])) {
209
+ const id = f.split('/').slice(-2)[0];
210
+ if (id.endsWith(exports.DISABLING_POSTFIX))
211
+ continue;
212
+ if (!exports.enablePlugins.get().includes(id)) {
213
+ try {
214
+ const source = await (0, promises_1.readFile)(f, 'utf8');
215
+ foundDisabled[id] = parsePluginSource(id, source);
216
+ }
217
+ catch (_a) { }
218
+ continue;
219
+ }
220
+ found.push(id);
221
+ if (plugins[id]) // already loaded
222
+ continue;
223
+ const module = path_1.default.resolve(f);
224
+ const { unwatch } = (0, watchLoad_1.watchLoad)(f, async () => {
225
+ try {
226
+ const alreadyRunning = plugins[id];
227
+ console.log(alreadyRunning ? "reloading plugin" : "loading plugin", id);
228
+ const { init, ...data } = await Promise.resolve().then(() => __importStar(require(module)));
229
+ delete data.default;
230
+ deleteModule(require.resolve(module)); // avoid caching at next import
231
+ calculateBadApi(data);
232
+ if (data.badApi)
233
+ console.log("plugin", id, data.badApi);
234
+ await (alreadyRunning === null || alreadyRunning === void 0 ? void 0 : alreadyRunning.unload(true));
235
+ console.debug("starting plugin", id);
236
+ const res = await (init === null || init === void 0 ? void 0 : init.call(null, {
237
+ srcDir: __dirname,
238
+ const: Const,
239
+ require,
240
+ getConnections: connections_1.getConnections,
241
+ events: events_1.default,
242
+ log(...args) {
243
+ console.log('plugin', id, ':', ...args);
244
+ },
245
+ getConfig: (cfgKey) => { var _a, _b, _c, _d, _e; return (_c = (_b = (_a = exports.pluginsConfig.get()) === null || _a === void 0 ? void 0 : _a[id]) === null || _b === void 0 ? void 0 : _b[cfgKey]) !== null && _c !== void 0 ? _c : (_e = (_d = data.config) === null || _d === void 0 ? void 0 : _d[cfgKey]) === null || _e === void 0 ? void 0 : _e.defaultValue; },
246
+ setConfig: (cfgKey, value) => setPluginConfig(id, { [cfgKey]: value }),
247
+ subscribeConfig(cfgKey, cb) {
248
+ let last = this.getConfig(cfgKey);
249
+ cb(last);
250
+ return exports.pluginsConfig.sub(() => {
251
+ const now = this.getConfig(cfgKey);
252
+ if ((0, misc_1.same)(now, last))
253
+ return;
254
+ try {
255
+ cb(last = now);
256
+ }
257
+ catch (e) {
258
+ console.log('plugin', id, String(e));
259
+ }
260
+ });
261
+ },
262
+ getHfsConfig: config_1.getConfig,
263
+ }));
264
+ Object.assign(data, res);
265
+ const plugin = new Plugin(id, data, unwatch);
266
+ if (alreadyRunning)
267
+ events_1.default.emit('pluginUpdated', Object.assign(lodash_1.default.pick(plugin, 'started'), getPluginInfo(id)));
268
+ else {
269
+ const wasInstalled = availablePlugins[id];
270
+ if (wasInstalled)
271
+ delete availablePlugins[id];
272
+ events_1.default.emit(wasInstalled ? 'pluginStarted' : 'pluginInstalled', plugin);
273
+ }
274
+ }
275
+ catch (e) {
276
+ console.log("plugin error:", e);
277
+ }
278
+ });
279
+ }
280
+ for (const id in foundDisabled) {
281
+ const p = foundDisabled[id];
282
+ const a = availablePlugins[id];
283
+ if ((0, misc_1.same)(a, p))
284
+ continue;
285
+ availablePlugins[id] = p;
286
+ if (a)
287
+ events_1.default.emit('pluginUpdated', p);
288
+ else if (!plugins[id])
289
+ events_1.default.emit('pluginInstalled', p);
290
+ }
291
+ for (const id in availablePlugins)
292
+ if (!foundDisabled[id] && !found.includes(id) && !plugins[id]) {
293
+ delete availablePlugins[id];
294
+ events_1.default.emit('pluginUninstalled', id);
295
+ }
296
+ for (const id in plugins)
297
+ if (!found.includes(id))
298
+ await plugins[id].unload();
299
+ }
300
+ exports.rescan = rescan;
301
+ function deleteModule(id) {
302
+ var _a;
303
+ const { cache } = require;
304
+ // build reversed map of dependencies
305
+ const requiredBy = { '.': ['.'] }; // don't touch main entry
306
+ for (const k in cache)
307
+ if (k !== id)
308
+ for (const child of (0, misc_1.wantArray)((_a = cache[k]) === null || _a === void 0 ? void 0 : _a.children))
309
+ (0, misc_1.getOrSet)(requiredBy, child.id, () => []).push(k);
310
+ const deleted = [];
311
+ recur(id);
312
+ function recur(id) {
313
+ let mod = cache[id];
314
+ if (!mod)
315
+ return;
316
+ delete cache[id];
317
+ deleted.push(id);
318
+ for (const child of mod.children)
319
+ if (!lodash_1.default.difference(requiredBy[child.id], deleted).length)
320
+ recur(child.id);
321
+ }
322
+ }
323
+ (0, misc_1.onProcessExit)(() => Promise.allSettled(mapPlugins(pl => pl.unload())));
324
+ function parsePluginSource(id, source) {
325
+ var _a, _b, _c, _d, _e, _f;
326
+ const pl = { id };
327
+ pl.description = (0, misc_1.tryJson)((_a = /exports.description *= *(".*")/.exec(source)) === null || _a === void 0 ? void 0 : _a[1]);
328
+ pl.repo = (_b = /exports.repo *= *"(.*)"/.exec(source)) === null || _b === void 0 ? void 0 : _b[1];
329
+ pl.version = (_d = Number((_c = /exports.version *= *(\d*\.?\d+)/.exec(source)) === null || _c === void 0 ? void 0 : _c[1])) !== null && _d !== void 0 ? _d : undefined;
330
+ pl.apiRequired = (_f = (0, misc_1.tryJson)((_e = /exports.apiRequired *= *([ \d.,[\]]+)/.exec(source)) === null || _e === void 0 ? void 0 : _e[1])) !== null && _f !== void 0 ? _f : undefined;
331
+ if (Array.isArray(pl.apiRequired) && (pl.apiRequired.length !== 2 || !pl.apiRequired.every(lodash_1.default.isFinite))) // validate [from,to] form
332
+ pl.apiRequired = undefined;
333
+ calculateBadApi(pl);
334
+ return pl;
335
+ }
336
+ exports.parsePluginSource = parsePluginSource;
337
+ function calculateBadApi(data) {
338
+ const r = data.apiRequired;
339
+ const [min, max] = Array.isArray(r) ? r : [r, r]; // normalize data type
340
+ data.badApi = min > const_1.API_VERSION ? "may not work correctly as it is designed for a newer version of HFS - check for updates"
341
+ : max < const_1.COMPATIBLE_API_VERSION ? "may not work correctly as it is designed for an older version of HFS - check for updates"
342
+ : undefined;
343
+ }
@@ -0,0 +1,105 @@
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 __importDefault = (this && this.__importDefault) || function (mod) {
4
+ return (mod && mod.__esModule) ? mod : { "default": mod };
5
+ };
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.getRange = exports.serveFile = exports.serveFileNode = void 0;
8
+ const fs_1 = require("fs");
9
+ const const_1 = require("./const");
10
+ const vfs_1 = require("./vfs");
11
+ const mime_types_1 = __importDefault(require("mime-types"));
12
+ const config_1 = require("./config");
13
+ const micromatch_1 = require("micromatch");
14
+ const lodash_1 = __importDefault(require("lodash"));
15
+ const path_1 = __importDefault(require("path"));
16
+ const util_1 = require("util");
17
+ const allowedReferer = (0, config_1.defineConfig)('allowed_referer', '');
18
+ function serveFileNode(node) {
19
+ const { source, mime } = node;
20
+ const name = (0, vfs_1.getNodeName)(node);
21
+ const mimeString = typeof mime === 'string' ? mime
22
+ : lodash_1.default.find(mime, (val, mask) => (0, micromatch_1.isMatch)(name, mask));
23
+ return (ctx, next) => {
24
+ var _a;
25
+ const allowed = allowedReferer.get();
26
+ if (allowed) {
27
+ const ref = (_a = /\/\/([^:/]+)/.exec(ctx.get('referer'))) === null || _a === void 0 ? void 0 : _a[1]; // extract host from url
28
+ if (ref && ref !== host() // automatic accept if referer is basically the hosting domain
29
+ && !(0, micromatch_1.isMatch)(ref, allowed))
30
+ return ctx.status = const_1.FORBIDDEN;
31
+ function host() {
32
+ const s = ctx.get('host');
33
+ return s[0] === '[' ? s.slice(1, s.indexOf(']')) : s === null || s === void 0 ? void 0 : s.split(':')[0];
34
+ }
35
+ }
36
+ ctx.vfsNode = node; // useful to tell service files from files shared by the user
37
+ return serveFile(source || '', mimeString)(ctx, next);
38
+ };
39
+ }
40
+ exports.serveFileNode = serveFileNode;
41
+ const mimeCfg = (0, config_1.defineConfig)('mime', { '*': 'auto' });
42
+ function serveFile(source, mime, content) {
43
+ return async (ctx) => {
44
+ if (!source)
45
+ return;
46
+ const fn = path_1.default.basename(source);
47
+ mime = mime !== null && mime !== void 0 ? mime : lodash_1.default.find(mimeCfg.get(), (v, k) => k > '' && (0, micromatch_1.isMatch)(fn, k)); // isMatch throws on an empty string
48
+ if (mime === vfs_1.MIME_AUTO)
49
+ mime = mime_types_1.default.lookup(source) || '';
50
+ if (mime)
51
+ ctx.type = mime;
52
+ if (ctx.method === 'OPTIONS') {
53
+ ctx.status = const_1.NO_CONTENT;
54
+ ctx.set({ Allow: 'OPTIONS, GET, HEAD' });
55
+ return;
56
+ }
57
+ if (ctx.method !== 'GET')
58
+ return ctx.status = const_1.METHOD_NOT_ALLOWED;
59
+ try {
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
+ ctx.set('Last-Modified', stats.mtime.toUTCString());
62
+ ctx.fileSource = source;
63
+ ctx.status = 200;
64
+ if (ctx.fresh)
65
+ return ctx.status = 304;
66
+ if (content !== undefined)
67
+ return ctx.body = content;
68
+ const range = getRange(ctx, stats.size);
69
+ ctx.body = (0, fs_1.createReadStream)(source, range);
70
+ }
71
+ catch (_a) {
72
+ return ctx.status = 404;
73
+ }
74
+ };
75
+ }
76
+ exports.serveFile = serveFile;
77
+ function getRange(ctx, totalSize) {
78
+ ctx.set('Accept-Ranges', 'bytes');
79
+ const { range } = ctx.request.header;
80
+ if (!range) {
81
+ ctx.response.length = totalSize;
82
+ return;
83
+ }
84
+ const ranges = range.split('=')[1];
85
+ if (ranges.includes(','))
86
+ return ctx.throw(400, 'multi-range not supported');
87
+ let bytes = ranges === null || ranges === void 0 ? void 0 : ranges.split('-');
88
+ if (!(bytes === null || bytes === void 0 ? void 0 : bytes.length))
89
+ return ctx.throw(400, 'bad range');
90
+ const max = totalSize - 1;
91
+ const start = bytes[0] ? Number(bytes[0]) : Math.max(0, totalSize - Number(bytes[1])); // a negative start is relative to the end
92
+ const end = bytes[0] ? Number(bytes[1] || max) : max;
93
+ // we don't support last-bytes without knowing max
94
+ if (isNaN(end) && isNaN(max) || end > max || start > max) {
95
+ ctx.status = 416;
96
+ ctx.set('Content-Range', `bytes ${totalSize}`);
97
+ ctx.body = 'Requested Range Not Satisfiable';
98
+ return;
99
+ }
100
+ ctx.status = 206;
101
+ ctx.set('Content-Range', `bytes ${start}-${isNaN(end) ? '' : end}/${isNaN(totalSize) ? '*' : totalSize}`);
102
+ ctx.response.length = end - start + 1;
103
+ return { start, end };
104
+ }
105
+ exports.getRange = getRange;