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
@@ -0,0 +1,128 @@
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 plugins_1 = require("./plugins");
8
+ const lodash_1 = __importDefault(require("lodash"));
9
+ const assert_1 = __importDefault(require("assert"));
10
+ const misc_1 = require("./misc");
11
+ const apiMiddleware_1 = require("./apiMiddleware");
12
+ const events_1 = __importDefault(require("./events"));
13
+ const promises_1 = require("fs/promises");
14
+ const github_1 = require("./github");
15
+ const apis = {
16
+ get_plugins({}, ctx) {
17
+ const list = new apiMiddleware_1.SendListReadable({ addAtStart: [...(0, plugins_1.mapPlugins)(serialize), ...(0, plugins_1.getAvailablePlugins)()] });
18
+ return list.events(ctx, {
19
+ pluginInstalled: p => list.add(serialize(p)),
20
+ 'pluginStarted pluginStopped pluginUpdated': p => {
21
+ const { id, ...rest } = serialize(p);
22
+ list.update({ id }, rest);
23
+ },
24
+ pluginUninstalled: id => list.remove({ id }),
25
+ });
26
+ function serialize(p) {
27
+ const o = 'getData' in p ? Object.assign(lodash_1.default.pick(p, ['id', 'started']), p.getData())
28
+ : { ...p }; // _.defaults mutates object, and we don't want that
29
+ return lodash_1.default.defaults(o, { started: null, badApi: null }); // nulls should be used to be sure to overwrite previous values,
30
+ }
31
+ },
32
+ async get_plugin_updates() {
33
+ const list = new apiMiddleware_1.SendListReadable();
34
+ setTimeout(async () => {
35
+ for (const [folder, repo] of Object.entries((0, github_1.getFolder2repo)()))
36
+ try {
37
+ if (!repo)
38
+ continue;
39
+ const online = await (0, github_1.readOnlinePlugin)(await (0, github_1.getRepoInfo)(repo));
40
+ if (!online.apiRequired || online.badApi)
41
+ continue;
42
+ const disk = (0, plugins_1.getPluginInfo)(folder);
43
+ if (online.version > disk.version)
44
+ list.add(online);
45
+ }
46
+ catch (err) {
47
+ list.error(err.code || err.message);
48
+ }
49
+ list.close();
50
+ });
51
+ return list;
52
+ },
53
+ async set_plugin({ id, enabled, config }) {
54
+ (0, assert_1.default)(id, 'id');
55
+ if (enabled !== undefined)
56
+ (0, plugins_1.enablePlugin)(id, enabled);
57
+ if (config)
58
+ (0, plugins_1.setPluginConfig)(id, config);
59
+ return {};
60
+ },
61
+ async get_plugin({ id }) {
62
+ return {
63
+ enabled: plugins_1.enablePlugins.get().includes(id),
64
+ config: {
65
+ ...(0, misc_1.objSameKeys)((0, plugins_1.getPluginConfigFields)(id) || {}, v => v === null || v === void 0 ? void 0 : v.defaultValue),
66
+ ...plugins_1.pluginsConfig.get()[id]
67
+ }
68
+ };
69
+ },
70
+ search_online_plugins({ text }, ctx) {
71
+ return new apiMiddleware_1.SendListReadable({
72
+ async doAtStart(list) {
73
+ try {
74
+ const folder2repo = (0, github_1.getFolder2repo)();
75
+ for await (const pl of (0, github_1.searchPlugins)(text)) {
76
+ const repo = pl.id;
77
+ const folder = lodash_1.default.findKey(folder2repo, x => x === repo);
78
+ const installed = folder && (0, plugins_1.getPluginInfo)(folder);
79
+ Object.assign(pl, {
80
+ installed: lodash_1.default.includes(folder2repo, repo),
81
+ update: installed && installed.version < pl.version,
82
+ });
83
+ list.add(pl);
84
+ // watch for events about this plugin, until this request is closed
85
+ ctx.req.on('close', (0, misc_1.onOff)(events_1.default, {
86
+ pluginInstalled: p => {
87
+ if (p.repo === repo)
88
+ list.update({ id: repo }, { installed: true });
89
+ },
90
+ pluginUninstalled: folder => {
91
+ if (repo === (0, github_1.getFolder2repo)()[folder])
92
+ list.update({ id: repo }, { installed: false });
93
+ },
94
+ pluginUpdated: p => {
95
+ if (p.repo === repo)
96
+ list.update({ id: repo }, { update: p.version < pl.version });
97
+ },
98
+ ['pluginDownload_' + repo](status) {
99
+ list.update({ id: repo }, { downloading: status !== null && status !== void 0 ? status : null });
100
+ }
101
+ }));
102
+ }
103
+ }
104
+ catch (err) {
105
+ list.error(err.code || err.message);
106
+ }
107
+ list.ready();
108
+ }
109
+ });
110
+ },
111
+ async download_plugin(pl) {
112
+ const res = await (0, github_1.downloadPlugin)(pl.id, pl.branch);
113
+ return typeof res === 'string' ? (0, plugins_1.getPluginInfo)(res) : res;
114
+ },
115
+ async update_plugin(pl) {
116
+ await (0, github_1.downloadPlugin)(pl.id, pl.branch, true);
117
+ return {};
118
+ },
119
+ async uninstall_plugin({ id }) {
120
+ while ((0, plugins_1.isPluginRunning)(id)) {
121
+ (0, plugins_1.enablePlugin)(id, false);
122
+ await (0, misc_1.wait)(500);
123
+ }
124
+ await (0, promises_1.rm)(plugins_1.PATH + '/' + id, { recursive: true, force: true });
125
+ return {};
126
+ }
127
+ };
128
+ exports.default = apis;
package/src/api.vfs.js ADDED
@@ -0,0 +1,167 @@
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 vfs_1 = require("./vfs");
8
+ const lodash_1 = __importDefault(require("lodash"));
9
+ const promises_1 = require("fs/promises");
10
+ const apiMiddleware_1 = require("./apiMiddleware");
11
+ const path_1 = require("path");
12
+ const misc_1 = require("./misc");
13
+ const child_process_1 = require("child_process");
14
+ const util_1 = require("util");
15
+ const const_1 = require("./const");
16
+ const micromatch_1 = require("micromatch");
17
+ // to manipulate the tree we need the original node
18
+ async function urlToNodeOriginal(uri) {
19
+ const n = await (0, vfs_1.urlToNode)(uri);
20
+ return (n === null || n === void 0 ? void 0 : n.isTemp) ? n.original : n;
21
+ }
22
+ const apis = {
23
+ async get_vfs() {
24
+ return {
25
+ root: vfs_1.vfs && await recur(vfs_1.vfs),
26
+ defaultPerms: vfs_1.defaultPerms,
27
+ };
28
+ async function recur(node) {
29
+ const dir = await (0, vfs_1.nodeIsDirectory)(node);
30
+ const stats = {};
31
+ try {
32
+ if (!dir)
33
+ Object.assign(stats, lodash_1.default.pick(await (0, promises_1.stat)(node.source), ['size', 'ctime', 'mtime']));
34
+ }
35
+ catch (_a) {
36
+ stats.size = -1;
37
+ }
38
+ if (stats && Number(stats.mtime) === Number(stats.ctime))
39
+ delete stats.mtime;
40
+ const isRoot = node === vfs_1.vfs;
41
+ return {
42
+ ...stats,
43
+ ...node,
44
+ website: dir && node.source && await (0, promises_1.stat)((0, path_1.join)(node.source, 'index.html')).then(() => true, () => undefined)
45
+ || undefined,
46
+ name: isRoot ? undefined : (0, vfs_1.getNodeName)(node),
47
+ type: dir ? 'folder' : undefined,
48
+ children: node.children && await Promise.all(node.children.map(recur)),
49
+ };
50
+ }
51
+ },
52
+ async set_vfs({ uri, props }) {
53
+ const n = await urlToNodeOriginal(uri);
54
+ if (!n)
55
+ return new apiMiddleware_1.ApiError(const_1.HTTP_NOT_FOUND, 'path not found');
56
+ props = pickProps(props, ['name', 'source', 'masks', 'default', ...Object.keys(vfs_1.defaultPerms)]);
57
+ props = (0, misc_1.objSameKeys)(props, v => v === null ? undefined : v); // null is a way to serialize undefined, that will restore default values
58
+ if (props.masks && typeof props.masks !== 'object')
59
+ delete props.masks;
60
+ Object.assign(n, props);
61
+ if ((0, vfs_1.getNodeName)(lodash_1.default.omit(n, ['name'])) === n.name) // name only if necessary
62
+ n.name = undefined;
63
+ await (0, vfs_1.saveVfs)();
64
+ return n;
65
+ },
66
+ async add_vfs({ under, source, name }) {
67
+ const n = under ? await urlToNodeOriginal(under) : vfs_1.vfs;
68
+ if (!n)
69
+ return new apiMiddleware_1.ApiError(const_1.HTTP_NOT_FOUND, 'invalid under');
70
+ if (n.isTemp || !await (0, vfs_1.nodeIsDirectory)(n))
71
+ return new apiMiddleware_1.ApiError(const_1.HTTP_FORBIDDEN, 'invalid under');
72
+ if ((0, misc_1.isWindowsDrive)(source))
73
+ source += '\\'; // slash must be included, otherwise it will refer to the cwd of that drive
74
+ const a = n.children || (n.children = []);
75
+ if (source && a.find(x => x.source === source))
76
+ return new apiMiddleware_1.ApiError(const_1.HTTP_CONFLICT, 'already present');
77
+ a.unshift({ source, name });
78
+ await (0, vfs_1.saveVfs)();
79
+ return {};
80
+ },
81
+ async del_vfs({ uris }) {
82
+ if (!uris || !Array.isArray(uris))
83
+ return new apiMiddleware_1.ApiError(const_1.HTTP_BAD_REQUEST, 'invalid uris');
84
+ return {
85
+ errors: await Promise.all(uris.map(async (uri) => {
86
+ if (typeof uri !== 'string')
87
+ return const_1.HTTP_BAD_REQUEST;
88
+ const node = await urlToNodeOriginal(uri);
89
+ if (!node)
90
+ return const_1.HTTP_NOT_FOUND;
91
+ const parent = (0, path_1.dirname)(uri);
92
+ const parentNode = await urlToNodeOriginal(parent);
93
+ if (!parentNode)
94
+ return const_1.HTTP_FORBIDDEN;
95
+ const { children } = parentNode;
96
+ if (!children) // shouldn't happen
97
+ return const_1.HTTP_SERVER_ERROR;
98
+ const idx = children.indexOf(node);
99
+ children.splice(idx, 1);
100
+ (0, vfs_1.saveVfs)();
101
+ return 0; // error code 0 is OK
102
+ }))
103
+ };
104
+ },
105
+ get_cwd() {
106
+ return { path: process.cwd() };
107
+ },
108
+ async resolve_path({ path, closestFolder }) {
109
+ path = (0, path_1.resolve)(path);
110
+ if (closestFolder)
111
+ while (path && !await (0, promises_1.stat)(path).then(x => x.isDirectory(), () => 0))
112
+ path = (0, path_1.dirname)(path);
113
+ return { path };
114
+ },
115
+ async *ls({ path, files = true, fileMask }, ctx) {
116
+ if (!path && const_1.IS_WINDOWS) {
117
+ try {
118
+ for (const n of await getDrives())
119
+ yield { add: { n, k: 'd' } };
120
+ }
121
+ catch (error) {
122
+ console.debug(error);
123
+ }
124
+ return;
125
+ }
126
+ try {
127
+ path = (0, misc_1.isWindowsDrive)(path) ? path + '\\' : (0, path_1.resolve)(path || '/');
128
+ for await (const name of (0, misc_1.dirStream)(path)) {
129
+ if (ctx.req.aborted)
130
+ return;
131
+ try {
132
+ const stats = await (0, promises_1.stat)((0, path_1.join)(path, name));
133
+ if (stats.isFile())
134
+ if (!files || fileMask && !(0, micromatch_1.isMatch)(name, fileMask))
135
+ continue;
136
+ yield {
137
+ add: {
138
+ n: name,
139
+ s: stats.size,
140
+ c: stats.ctime,
141
+ m: stats.mtime,
142
+ k: stats.isDirectory() ? 'd' : undefined,
143
+ }
144
+ };
145
+ }
146
+ catch (_a) { } // just ignore entries we can't stat
147
+ }
148
+ }
149
+ catch (e) {
150
+ yield { error: e.code || e.message || String(e) };
151
+ }
152
+ }
153
+ };
154
+ exports.default = apis;
155
+ // pick only selected props, and consider null and empty string as undefined
156
+ function pickProps(o, keys) {
157
+ const ret = {};
158
+ if (o && typeof o === 'object')
159
+ for (const k of keys)
160
+ if (k in o)
161
+ ret[k] = o[k] === null || o[k] === '' ? undefined : o[k];
162
+ return ret;
163
+ }
164
+ async function getDrives() {
165
+ const { stdout } = await (0, util_1.promisify)(child_process_1.exec)('wmic logicaldisk get name');
166
+ return stdout.split('\n').slice(1).map(x => x.trim()).filter(Boolean);
167
+ }
@@ -0,0 +1,123 @@
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.SendListReadable = exports.apiMiddleware = exports.ApiError = void 0;
8
+ const sse_1 = __importDefault(require("./sse"));
9
+ const stream_1 = require("stream");
10
+ const misc_1 = require("./misc");
11
+ const events_1 = __importDefault(require("./events"));
12
+ const const_1 = require("./const");
13
+ const lodash_1 = __importDefault(require("lodash"));
14
+ class ApiError extends Error {
15
+ constructor(status, message) {
16
+ super(typeof message === 'string' ? message : message === null || message === void 0 ? void 0 : message.message);
17
+ this.status = status;
18
+ }
19
+ }
20
+ exports.ApiError = ApiError;
21
+ function apiMiddleware(apis) {
22
+ return async (ctx) => {
23
+ const { params } = ctx;
24
+ console.debug('API', ctx.method, ctx.path, { ...params });
25
+ if (!apis.hasOwnProperty(ctx.path)) {
26
+ ctx.body = 'invalid api';
27
+ return ctx.status = const_1.HTTP_NOT_FOUND;
28
+ }
29
+ const csrf = ctx.cookies.get('csrf');
30
+ // we don't rely on SameSite cookie option because it's https-only
31
+ let res = csrf && csrf !== params.csrf ? new ApiError(const_1.HTTP_UNAUTHORIZED, 'csrf')
32
+ : await apis[ctx.path](params || {}, ctx);
33
+ if (isAsyncGenerator(res))
34
+ res = (0, misc_1.asyncGeneratorToReadable)(res);
35
+ if (res instanceof stream_1.Readable) { // Readable, we'll go SSE-mode
36
+ res.pipe((0, sse_1.default)(ctx));
37
+ const stillRes = res; // satisfy ts
38
+ 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());
40
+ return;
41
+ }
42
+ if (res instanceof ApiError) {
43
+ ctx.body = res.message;
44
+ return ctx.status = res.status;
45
+ }
46
+ if (res instanceof Error) { // generic exception
47
+ ctx.body = String(res);
48
+ return ctx.status = const_1.HTTP_BAD_REQUEST;
49
+ }
50
+ ctx.body = res;
51
+ };
52
+ }
53
+ exports.apiMiddleware = apiMiddleware;
54
+ function isAsyncGenerator(x) {
55
+ return typeof (x === null || x === void 0 ? void 0 : x.next) === 'function';
56
+ }
57
+ class SendListReadable extends stream_1.Readable {
58
+ constructor({ addAtStart, doAtStart, bufferTime } = {}) {
59
+ super({ objectMode: true, read() { } });
60
+ this.buffer = [];
61
+ if (!bufferTime)
62
+ bufferTime = 100;
63
+ this.processBuffer = lodash_1.default.debounce(() => {
64
+ this.push(this.buffer);
65
+ this.buffer = [];
66
+ }, bufferTime, { maxWait: bufferTime });
67
+ this.on('end', () => this.destroy());
68
+ if (doAtStart)
69
+ setTimeout(() => doAtStart(this)); // work later, when list object has been received by Koa
70
+ if (addAtStart) {
71
+ for (const x of addAtStart)
72
+ this.add(x);
73
+ this.ready();
74
+ }
75
+ }
76
+ _push(rec) {
77
+ this.buffer.push(rec);
78
+ if (this.buffer.length > 10000) // hard limit
79
+ this.processBuffer.flush();
80
+ else
81
+ this.processBuffer();
82
+ }
83
+ add(rec) {
84
+ this._push({ add: rec });
85
+ }
86
+ remove(key) {
87
+ this._push({ remove: [key] });
88
+ }
89
+ update(search, change) {
90
+ this._push({ update: [{ search, change }] });
91
+ }
92
+ ready() {
93
+ this._push('ready');
94
+ }
95
+ custom(data) {
96
+ this._push(data);
97
+ }
98
+ props(props) {
99
+ this._push({ props });
100
+ }
101
+ error(msg, close = false) {
102
+ this._push({ error: msg });
103
+ this.lastError = msg;
104
+ if (close)
105
+ this.close();
106
+ }
107
+ getLastError() {
108
+ return this.lastError;
109
+ }
110
+ close() {
111
+ this.processBuffer.flush();
112
+ this.push(null);
113
+ }
114
+ events(ctx, eventMap) {
115
+ const off = (0, misc_1.onOff)(events_1.default, eventMap);
116
+ ctx.res.once('close', off);
117
+ return this;
118
+ }
119
+ isClosed() {
120
+ return this.destroyed;
121
+ }
122
+ }
123
+ exports.SendListReadable = SendListReadable;
package/src/block.js ADDED
@@ -0,0 +1,34 @@
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.applyBlock = void 0;
8
+ const config_1 = require("./config");
9
+ const connections_1 = require("./connections");
10
+ const misc_1 = require("./misc");
11
+ const cidr_tools_1 = __importDefault(require("cidr-tools"));
12
+ const lodash_1 = __importDefault(require("lodash"));
13
+ (0, config_1.defineConfig)('block', []).sub(rules => {
14
+ compileBlock(rules);
15
+ for (const { socket, ip } of (0, connections_1.getConnections)())
16
+ applyBlock(socket, ip);
17
+ });
18
+ let blockFunctions = []; // "compiled" versions of the rules in config.block
19
+ function compileBlock(rules) {
20
+ blockFunctions = !Array.isArray(rules) ? []
21
+ : (0, misc_1.onlyTruthy)(rules.map(rule => !rule ? null
22
+ : (0, misc_1.with_)(rule.ip, ip => typeof ip !== 'string' ? null
23
+ : ip.includes('/') ? x => cidr_tools_1.default.contains(ip, x)
24
+ : ip.includes('*') ? (0, misc_1.with_)(ipMask2regExp(ip), re => x => re.test(x))
25
+ : x => x === ip)));
26
+ function ipMask2regExp(ipMask) {
27
+ return new RegExp(lodash_1.default.escapeRegExp(ipMask).replace(/\\\*/g, '.*'));
28
+ }
29
+ }
30
+ function applyBlock(socket, ip = (0, connections_1.normalizeIp)(socket.remoteAddress || '')) {
31
+ if (ip && blockFunctions.find(rule => rule(ip)))
32
+ return socket.destroy();
33
+ }
34
+ exports.applyBlock = applyBlock;
@@ -0,0 +1,125 @@
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 perm_1 = require("./perm");
8
+ const config_1 = require("./config");
9
+ const lodash_1 = __importDefault(require("lodash"));
10
+ const update_1 = require("./update");
11
+ const listen_1 = require("./listen");
12
+ const yaml_1 = __importDefault(require("yaml"));
13
+ const const_1 = require("./const");
14
+ const readline_1 = require("readline");
15
+ try {
16
+ /*
17
+ is this try-block useful in case the stdin is unavailable?
18
+ Not sure, but someone reported a problem using nohup https://github.com/rejetto/hfs/issues/74
19
+ and I've found this example try-catching https://github.com/DefinitelyTyped/DefinitelyTyped/blob/dda83a906914489e09ca28afea12948529015d4a/types/node/readline.d.ts#L489
20
+ */
21
+ (0, readline_1.createInterface)({ input: process.stdin }).on('line', parseCommandLine);
22
+ console.log(`HINT: type "help" for help`);
23
+ }
24
+ catch (_a) {
25
+ console.log("console commands not available");
26
+ }
27
+ function parseCommandLine(line) {
28
+ if (!line)
29
+ return;
30
+ const [name, ...params] = line.trim().split(/ +/);
31
+ const cmd = commands[name];
32
+ if (!cmd)
33
+ return console.error("cannot understand entered command, try 'help'");
34
+ if (cmd.cb.length > params.length)
35
+ return console.error("insufficient parameters, expected: " + cmd.params);
36
+ cmd.cb(...params).then(() => console.log("+++ command executed"), (err) => {
37
+ if (typeof err === 'string')
38
+ console.error("command failed:", err);
39
+ else
40
+ throw err;
41
+ });
42
+ }
43
+ const commands = {
44
+ help: {
45
+ params: '',
46
+ async cb() {
47
+ console.log("supported commands:", ...lodash_1.default.map(commands, ({ params }, name) => '\n - ' + name + ' ' + params));
48
+ }
49
+ },
50
+ 'show-admin': {
51
+ params: '',
52
+ cb() {
53
+ (0, listen_1.openAdmin)();
54
+ }
55
+ },
56
+ 'create-admin': {
57
+ params: '<password> [<username>=admin]',
58
+ async cb(password, username = 'admin') {
59
+ if ((0, perm_1.getAccount)(username))
60
+ throw `user ${username} already exists`;
61
+ const acc = (0, perm_1.addAccount)(username, { admin: true });
62
+ await (0, perm_1.updateAccount)(acc, acc => {
63
+ acc.password = password;
64
+ });
65
+ }
66
+ },
67
+ 'change-password': {
68
+ params: '<user> <password>',
69
+ async cb(user, password) {
70
+ const acc = (0, perm_1.getAccount)(user);
71
+ if (!acc)
72
+ throw "user doesn't exist";
73
+ await (0, perm_1.updateAccount)(acc, acc => {
74
+ acc.password = password;
75
+ });
76
+ }
77
+ },
78
+ config: {
79
+ params: '<key> <value>',
80
+ async cb(key, value) {
81
+ const conf = (0, config_1.getConfigDefinition)(key);
82
+ if (!conf)
83
+ throw "specified key doesn't exist";
84
+ let v = value;
85
+ try {
86
+ v = JSON.parse(v);
87
+ }
88
+ catch (_a) { }
89
+ (0, config_1.setConfig)({ [key]: v });
90
+ }
91
+ },
92
+ 'show-config': {
93
+ params: '<key>',
94
+ async cb(key) {
95
+ const conf = (0, config_1.getConfigDefinition)(key);
96
+ if (!conf)
97
+ throw "specified key doesn't exist";
98
+ console.log(yaml_1.default.stringify((0, config_1.getConfig)(key), { lineWidth: 1000 }).trim());
99
+ }
100
+ },
101
+ quit: {
102
+ params: '',
103
+ async cb() {
104
+ process.exit(0);
105
+ }
106
+ },
107
+ update: {
108
+ params: '',
109
+ cb: update_1.update
110
+ },
111
+ 'check-update': {
112
+ params: '',
113
+ async cb() {
114
+ const update = await (0, update_1.getUpdate)();
115
+ console.log("new version available", update.name);
116
+ }
117
+ },
118
+ version: {
119
+ params: '',
120
+ async cb() {
121
+ console.log(const_1.VERSION);
122
+ console.log(const_1.BUILD_TIMESTAMP);
123
+ }
124
+ },
125
+ };