hfs 0.26.7 → 0.26.8
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.
- package/admin/.DS_Store +0 -0
- package/admin/.eslintrc +8 -0
- package/admin/.gitignore +23 -0
- package/admin/index.html +1 -3
- package/admin/package.json +67 -0
- package/admin/{logo.svg → public/logo.svg} +0 -0
- package/admin/src/AccountForm.ts +92 -0
- package/admin/src/AccountsPage.ts +143 -0
- package/admin/src/App.ts +83 -0
- package/admin/src/ArrayField.ts +84 -0
- package/admin/src/ConfigPage.ts +279 -0
- package/admin/src/FileField.ts +52 -0
- package/admin/src/FileForm.ts +148 -0
- package/admin/src/FilePicker.ts +166 -0
- package/admin/src/HomePage.ts +96 -0
- package/admin/src/InstalledPlugins.ts +158 -0
- package/admin/src/LoginRequired.ts +75 -0
- package/admin/src/LogoutPage.ts +27 -0
- package/admin/src/LogsPage.ts +75 -0
- package/admin/src/MainMenu.ts +74 -0
- package/admin/src/MenuButton.ts +38 -0
- package/admin/src/MonitorPage.ts +200 -0
- package/admin/src/OnlinePlugins.ts +101 -0
- package/admin/src/PermField.ts +80 -0
- package/admin/src/PluginsPage.ts +27 -0
- package/admin/src/VfsMenuBar.ts +58 -0
- package/admin/src/VfsPage.ts +124 -0
- package/admin/src/VfsTree.ts +95 -0
- package/admin/src/addFiles.ts +59 -0
- package/admin/src/api.ts +246 -0
- package/admin/src/dialog.ts +203 -0
- package/admin/src/index.css +21 -0
- package/admin/src/index.ts +10 -0
- package/admin/src/md.ts +31 -0
- package/admin/src/misc.ts +141 -0
- package/admin/src/react-app-env.d.ts +1 -0
- package/admin/src/reportWebVitals.ts +15 -0
- package/admin/src/setupTests.ts +5 -0
- package/admin/src/state.ts +40 -0
- package/admin/src/theme.ts +37 -0
- package/admin/tsconfig.json +26 -0
- package/admin/vite.config.ts +32 -0
- package/frontend/.DS_Store +0 -0
- package/frontend/.eslintrc +8 -0
- package/frontend/.gitignore +23 -0
- package/frontend/index.html +1 -3
- package/frontend/package.json +51 -0
- package/frontend/{fontello.css → public/fontello.css} +0 -0
- package/frontend/{fontello.woff2 → public/fontello.woff2} +0 -0
- package/frontend/src/App.ts +25 -0
- package/frontend/src/Breadcrumbs.ts +43 -0
- package/frontend/src/BrowseFiles.ts +141 -0
- package/frontend/src/Head.ts +45 -0
- package/frontend/src/UserPanel.ts +52 -0
- package/frontend/src/api.ts +78 -0
- package/frontend/src/components.ts +54 -0
- package/frontend/src/dialog.css +76 -0
- package/frontend/src/dialog.ts +105 -0
- package/frontend/src/icons.ts +46 -0
- package/frontend/src/index.scss +307 -0
- package/frontend/src/index.ts +10 -0
- package/frontend/src/login.ts +50 -0
- package/frontend/src/menu.ts +188 -0
- package/frontend/src/misc.ts +54 -0
- package/frontend/src/options.ts +52 -0
- package/frontend/src/react-app-env.d.ts +1 -0
- package/frontend/src/reportWebVitals.ts +15 -0
- package/frontend/src/setupTests.ts +5 -0
- package/frontend/src/state.ts +82 -0
- package/frontend/src/useAuthorized.ts +17 -0
- package/frontend/src/useFetchList.ts +144 -0
- package/frontend/src/useTheme.ts +23 -0
- package/frontend/tsconfig.json +26 -0
- package/frontend/vite.config.ts +21 -0
- package/package.json +2 -1
- package/plugins/vhosting/plugin.js +1 -1
- package/src/QuickZipStream.ts +279 -0
- package/src/ThrottledStream.ts +98 -0
- package/src/adminApis.ts +161 -0
- package/src/api.accounts.ts +78 -0
- package/src/api.auth.ts +131 -0
- package/src/api.file_list.ts +102 -0
- package/src/api.helpers.ts +30 -0
- package/src/api.monitor.ts +106 -0
- package/src/api.plugins.ts +139 -0
- package/src/api.vfs.ts +182 -0
- package/src/apiMiddleware.ts +124 -0
- package/src/block.ts +35 -0
- package/src/commands.ts +122 -0
- package/src/config.ts +166 -0
- package/src/connections.ts +60 -0
- package/src/const.ts +57 -0
- package/src/crypt.ts +16 -0
- package/src/debounceAsync.ts +51 -0
- package/src/events.ts +6 -0
- package/src/frontEndApis.ts +17 -0
- package/src/github.ts +102 -0
- package/src/index.ts +53 -0
- package/src/listen.ts +220 -0
- package/src/log.ts +128 -0
- package/src/middlewares.ts +176 -0
- package/src/misc.ts +149 -0
- package/src/pbkdf2.ts +83 -0
- package/src/perm.ts +194 -0
- package/src/plugins.ts +342 -0
- package/src/serveFile.ts +104 -0
- package/src/serveGuiFiles.ts +95 -0
- package/src/sse.ts +29 -0
- package/src/throttler.ts +106 -0
- package/src/update.ts +67 -0
- package/src/util-files.ts +137 -0
- package/src/util-generators.ts +29 -0
- package/src/util-http.ts +29 -0
- package/src/vfs.ts +258 -0
- package/src/watchLoad.ts +75 -0
- package/src/zip.ts +69 -0
- package/admin/assets/index.0f549e00.js +0 -281
- package/admin/assets/index.dcc78777.css +0 -1
- package/admin/assets/sha512.ea1121b3.js +0 -8
- package/frontend/assets/index.1151988f.js +0 -85
- package/frontend/assets/index.93366732.css +0 -1
- package/frontend/assets/sha512.bb881250.js +0 -8
- package/src/QuickZipStream.js +0 -285
- package/src/ThrottledStream.js +0 -93
- package/src/adminApis.js +0 -169
- package/src/api.accounts.js +0 -59
- package/src/api.auth.js +0 -130
- package/src/api.file_list.js +0 -103
- package/src/api.helpers.js +0 -32
- package/src/api.monitor.js +0 -102
- package/src/api.plugins.js +0 -127
- package/src/api.vfs.js +0 -164
- package/src/apiMiddleware.js +0 -136
- package/src/block.js +0 -33
- package/src/commands.js +0 -124
- package/src/config.js +0 -168
- package/src/connections.js +0 -57
- package/src/const.js +0 -83
- package/src/crypt.js +0 -21
- package/src/debounceAsync.js +0 -48
- package/src/events.js +0 -9
- package/src/frontEndApis.js +0 -38
- package/src/github.js +0 -102
- package/src/index.js +0 -55
- package/src/listen.js +0 -235
- package/src/log.js +0 -137
- package/src/middlewares.js +0 -154
- package/src/misc.js +0 -160
- package/src/pbkdf2.js +0 -74
- package/src/perm.js +0 -176
- package/src/plugins.js +0 -343
- package/src/serveFile.js +0 -104
- package/src/serveGuiFiles.js +0 -113
- package/src/sse.js +0 -29
- package/src/throttler.js +0 -91
- package/src/update.js +0 -69
- package/src/util-files.js +0 -148
- package/src/util-generators.js +0 -30
- package/src/util-http.js +0 -30
- package/src/vfs.js +0 -227
- package/src/watchLoad.js +0 -73
- package/src/zip.js +0 -69
package/src/api.vfs.js
DELETED
|
@@ -1,164 +0,0 @@
|
|
|
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
|
-
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 { root: vfs_1.vfs && await recur(vfs_1.vfs) };
|
|
25
|
-
async function recur(node) {
|
|
26
|
-
const dir = await (0, vfs_1.nodeIsDirectory)(node);
|
|
27
|
-
const stats = {};
|
|
28
|
-
try {
|
|
29
|
-
if (!dir)
|
|
30
|
-
Object.assign(stats, lodash_1.default.pick(await (0, promises_1.stat)(node.source), ['size', 'ctime', 'mtime']));
|
|
31
|
-
}
|
|
32
|
-
catch (_a) {
|
|
33
|
-
stats.size = -1;
|
|
34
|
-
}
|
|
35
|
-
if (stats && Number(stats.mtime) === Number(stats.ctime))
|
|
36
|
-
delete stats.mtime;
|
|
37
|
-
const isRoot = node === vfs_1.vfs;
|
|
38
|
-
return {
|
|
39
|
-
...stats,
|
|
40
|
-
...node,
|
|
41
|
-
website: dir && node.source && await (0, promises_1.stat)((0, path_1.join)(node.source, 'index.html')).then(() => true, () => undefined)
|
|
42
|
-
|| undefined,
|
|
43
|
-
name: isRoot ? undefined : (0, vfs_1.getNodeName)(node),
|
|
44
|
-
type: dir ? 'folder' : undefined,
|
|
45
|
-
children: node.children && await Promise.all(node.children.map(recur)),
|
|
46
|
-
};
|
|
47
|
-
}
|
|
48
|
-
},
|
|
49
|
-
async set_vfs({ uri, props }) {
|
|
50
|
-
const n = await urlToNodeOriginal(uri);
|
|
51
|
-
if (!n)
|
|
52
|
-
return new apiMiddleware_1.ApiError(404, 'path not found');
|
|
53
|
-
props = pickProps(props, ['name', 'source', 'can_see', 'can_read', 'masks', 'default']);
|
|
54
|
-
props = (0, misc_1.objSameKeys)(props, v => v === null ? undefined : v); // null is a way to serialize undefined, that will restore default values
|
|
55
|
-
if (props.masks && typeof props.masks !== 'object')
|
|
56
|
-
delete props.masks;
|
|
57
|
-
Object.assign(n, props);
|
|
58
|
-
if ((0, vfs_1.getNodeName)(lodash_1.default.omit(n, ['name'])) === n.name) // name only if necessary
|
|
59
|
-
n.name = undefined;
|
|
60
|
-
await (0, vfs_1.saveVfs)();
|
|
61
|
-
return n;
|
|
62
|
-
},
|
|
63
|
-
async add_vfs({ under, source, name }) {
|
|
64
|
-
const n = under ? await urlToNodeOriginal(under) : vfs_1.vfs;
|
|
65
|
-
if (!n)
|
|
66
|
-
return new apiMiddleware_1.ApiError(404, 'invalid under');
|
|
67
|
-
if (n.isTemp || !await (0, vfs_1.nodeIsDirectory)(n))
|
|
68
|
-
return new apiMiddleware_1.ApiError(const_1.FORBIDDEN, 'invalid under');
|
|
69
|
-
if ((0, misc_1.isWindowsDrive)(source))
|
|
70
|
-
source += '\\'; // slash must be included, otherwise it will refer to the cwd of that drive
|
|
71
|
-
const a = n.children || (n.children = []);
|
|
72
|
-
if (source && a.find(x => x.source === source))
|
|
73
|
-
return new apiMiddleware_1.ApiError(409, 'already present');
|
|
74
|
-
a.unshift({ source, name });
|
|
75
|
-
await (0, vfs_1.saveVfs)();
|
|
76
|
-
return {};
|
|
77
|
-
},
|
|
78
|
-
async del_vfs({ uris }) {
|
|
79
|
-
if (!uris || !Array.isArray(uris))
|
|
80
|
-
return new apiMiddleware_1.ApiError(400, 'invalid uris');
|
|
81
|
-
return {
|
|
82
|
-
errors: await Promise.all(uris.map(async (uri) => {
|
|
83
|
-
if (typeof uri !== 'string')
|
|
84
|
-
return 400;
|
|
85
|
-
const node = await urlToNodeOriginal(uri);
|
|
86
|
-
if (!node)
|
|
87
|
-
return 404;
|
|
88
|
-
const parent = (0, path_1.dirname)(uri);
|
|
89
|
-
const parentNode = await urlToNodeOriginal(parent);
|
|
90
|
-
if (!parentNode)
|
|
91
|
-
return const_1.FORBIDDEN;
|
|
92
|
-
const { children } = parentNode;
|
|
93
|
-
if (!children) // shouldn't happen
|
|
94
|
-
return 500;
|
|
95
|
-
const idx = children.indexOf(node);
|
|
96
|
-
children.splice(idx, 1);
|
|
97
|
-
(0, vfs_1.saveVfs)();
|
|
98
|
-
return 0; // error code 0 is OK
|
|
99
|
-
}))
|
|
100
|
-
};
|
|
101
|
-
},
|
|
102
|
-
get_cwd() {
|
|
103
|
-
return { path: process.cwd() };
|
|
104
|
-
},
|
|
105
|
-
async resolve_path({ path, closestFolder }) {
|
|
106
|
-
path = (0, path_1.resolve)(path);
|
|
107
|
-
if (closestFolder)
|
|
108
|
-
while (path && !await (0, promises_1.stat)(path).then(x => x.isDirectory(), () => 0))
|
|
109
|
-
path = (0, path_1.dirname)(path);
|
|
110
|
-
return { path };
|
|
111
|
-
},
|
|
112
|
-
async *ls({ path, files = true, fileMask }, ctx) {
|
|
113
|
-
if (!path && const_1.IS_WINDOWS) {
|
|
114
|
-
try {
|
|
115
|
-
for (const n of await getDrives())
|
|
116
|
-
yield { add: { n, k: 'd' } };
|
|
117
|
-
}
|
|
118
|
-
catch (error) {
|
|
119
|
-
console.debug(error);
|
|
120
|
-
}
|
|
121
|
-
return;
|
|
122
|
-
}
|
|
123
|
-
try {
|
|
124
|
-
path = (0, misc_1.isWindowsDrive)(path) ? path + '\\' : (0, path_1.resolve)(path || '/');
|
|
125
|
-
for await (const name of (0, misc_1.dirStream)(path)) {
|
|
126
|
-
if (ctx.req.aborted)
|
|
127
|
-
return;
|
|
128
|
-
try {
|
|
129
|
-
const stats = await (0, promises_1.stat)((0, path_1.join)(path, name));
|
|
130
|
-
if (stats.isFile())
|
|
131
|
-
if (!files || fileMask && !(0, micromatch_1.isMatch)(name, fileMask))
|
|
132
|
-
continue;
|
|
133
|
-
yield {
|
|
134
|
-
add: {
|
|
135
|
-
n: name,
|
|
136
|
-
s: stats.size,
|
|
137
|
-
c: stats.ctime,
|
|
138
|
-
m: stats.mtime,
|
|
139
|
-
k: stats.isDirectory() ? 'd' : undefined,
|
|
140
|
-
}
|
|
141
|
-
};
|
|
142
|
-
}
|
|
143
|
-
catch (_a) { } // just ignore entries we can't stat
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
catch (e) {
|
|
147
|
-
yield { error: e.code || e.message || String(e) };
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
};
|
|
151
|
-
exports.default = apis;
|
|
152
|
-
// pick only selected props, and consider null and empty string as undefined
|
|
153
|
-
function pickProps(o, keys) {
|
|
154
|
-
const ret = {};
|
|
155
|
-
if (o && typeof o === 'object')
|
|
156
|
-
for (const k of keys)
|
|
157
|
-
if (k in o)
|
|
158
|
-
ret[k] = o[k] === null || o[k] === '' ? undefined : o[k];
|
|
159
|
-
return ret;
|
|
160
|
-
}
|
|
161
|
-
async function getDrives() {
|
|
162
|
-
const { stdout } = await (0, util_1.promisify)(child_process_1.exec)('wmic logicaldisk get name');
|
|
163
|
-
return stdout.split('\n').slice(1).map(x => x.trim()).filter(Boolean);
|
|
164
|
-
}
|
package/src/apiMiddleware.js
DELETED
|
@@ -1,136 +0,0 @@
|
|
|
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.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.method === 'POST' ? await getJsonFromReq(ctx.req)
|
|
24
|
-
: (0, misc_1.objSameKeys)(ctx.request.query, x => Array.isArray(x) ? x : (0, misc_1.tryJson)(x));
|
|
25
|
-
console.debug('API', ctx.method, ctx.path, { ...params });
|
|
26
|
-
if (!apis.hasOwnProperty(ctx.path)) {
|
|
27
|
-
ctx.body = 'invalid api';
|
|
28
|
-
return ctx.status = 404;
|
|
29
|
-
}
|
|
30
|
-
const csrf = ctx.cookies.get('csrf');
|
|
31
|
-
// we don't rely on SameSite cookie option because it's https-only
|
|
32
|
-
let res = csrf && csrf !== params.csrf ? new ApiError(const_1.UNAUTHORIZED, 'csrf')
|
|
33
|
-
: await apis[ctx.path](params || {}, ctx);
|
|
34
|
-
if (isAsyncGenerator(res))
|
|
35
|
-
res = (0, misc_1.asyncGeneratorToReadable)(res);
|
|
36
|
-
if (res instanceof stream_1.Readable) { // Readable, we'll go SSE-mode
|
|
37
|
-
res.pipe((0, sse_1.default)(ctx));
|
|
38
|
-
const stillRes = res; // satisfy ts
|
|
39
|
-
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
|
|
40
|
-
stillRes.destroy());
|
|
41
|
-
return;
|
|
42
|
-
}
|
|
43
|
-
if (res instanceof ApiError) {
|
|
44
|
-
ctx.body = res.message;
|
|
45
|
-
return ctx.status = res.status;
|
|
46
|
-
}
|
|
47
|
-
if (res instanceof Error) { // generic exception
|
|
48
|
-
ctx.body = String(res);
|
|
49
|
-
return ctx.status = 400;
|
|
50
|
-
}
|
|
51
|
-
ctx.body = res;
|
|
52
|
-
};
|
|
53
|
-
}
|
|
54
|
-
exports.apiMiddleware = apiMiddleware;
|
|
55
|
-
function isAsyncGenerator(x) {
|
|
56
|
-
return typeof (x === null || x === void 0 ? void 0 : x.next) === 'function';
|
|
57
|
-
}
|
|
58
|
-
async function getJsonFromReq(req) {
|
|
59
|
-
return new Promise((resolve, reject) => {
|
|
60
|
-
let data = '';
|
|
61
|
-
req.on('data', chunk => data += chunk);
|
|
62
|
-
req.on('error', reject);
|
|
63
|
-
req.on('end', () => {
|
|
64
|
-
try {
|
|
65
|
-
resolve(data && JSON.parse(data));
|
|
66
|
-
}
|
|
67
|
-
catch (e) {
|
|
68
|
-
reject(e);
|
|
69
|
-
}
|
|
70
|
-
});
|
|
71
|
-
});
|
|
72
|
-
}
|
|
73
|
-
class SendListReadable extends stream_1.Readable {
|
|
74
|
-
constructor({ addAtStart, doAtStart, bufferTime } = {}) {
|
|
75
|
-
super({ objectMode: true, read() { } });
|
|
76
|
-
this.buffer = [];
|
|
77
|
-
if (!bufferTime)
|
|
78
|
-
bufferTime = 100;
|
|
79
|
-
this.processBuffer = lodash_1.default.debounce(() => {
|
|
80
|
-
this.push(this.buffer);
|
|
81
|
-
this.buffer = [];
|
|
82
|
-
}, bufferTime, { maxWait: bufferTime });
|
|
83
|
-
this.on('end', () => this.destroy());
|
|
84
|
-
if (doAtStart)
|
|
85
|
-
setTimeout(() => doAtStart(this)); // work later, when list object has been received by Koa
|
|
86
|
-
if (addAtStart) {
|
|
87
|
-
for (const x of addAtStart)
|
|
88
|
-
this.add(x);
|
|
89
|
-
this.ready();
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
_push(rec) {
|
|
93
|
-
this.buffer.push(rec);
|
|
94
|
-
if (this.buffer.length > 10000) // hard limit
|
|
95
|
-
this.processBuffer.flush();
|
|
96
|
-
else
|
|
97
|
-
this.processBuffer();
|
|
98
|
-
}
|
|
99
|
-
add(rec) {
|
|
100
|
-
this._push({ add: rec });
|
|
101
|
-
}
|
|
102
|
-
remove(key) {
|
|
103
|
-
this._push({ remove: [key] });
|
|
104
|
-
}
|
|
105
|
-
update(search, change) {
|
|
106
|
-
this._push({ update: [{ search, change }] });
|
|
107
|
-
}
|
|
108
|
-
ready() {
|
|
109
|
-
this._push('ready');
|
|
110
|
-
}
|
|
111
|
-
custom(data) {
|
|
112
|
-
this._push(data);
|
|
113
|
-
}
|
|
114
|
-
error(msg, close = false) {
|
|
115
|
-
this._push({ error: msg });
|
|
116
|
-
this.lastError = msg;
|
|
117
|
-
if (close)
|
|
118
|
-
this.close();
|
|
119
|
-
}
|
|
120
|
-
getLastError() {
|
|
121
|
-
return this.lastError;
|
|
122
|
-
}
|
|
123
|
-
close() {
|
|
124
|
-
this.processBuffer.flush();
|
|
125
|
-
this.push(null);
|
|
126
|
-
}
|
|
127
|
-
events(ctx, eventMap) {
|
|
128
|
-
const off = (0, misc_1.onOff)(events_1.default, eventMap);
|
|
129
|
-
ctx.res.once('close', off);
|
|
130
|
-
return this;
|
|
131
|
-
}
|
|
132
|
-
isClosed() {
|
|
133
|
-
return this.destroyed;
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
exports.SendListReadable = SendListReadable;
|
package/src/block.js
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.applyBlock = void 0;
|
|
7
|
-
const config_1 = require("./config");
|
|
8
|
-
const connections_1 = require("./connections");
|
|
9
|
-
const misc_1 = require("./misc");
|
|
10
|
-
const cidr_tools_1 = __importDefault(require("cidr-tools"));
|
|
11
|
-
const lodash_1 = __importDefault(require("lodash"));
|
|
12
|
-
(0, config_1.defineConfig)('block', []).sub(rules => {
|
|
13
|
-
compileBlock(rules);
|
|
14
|
-
for (const { socket, ip } of (0, connections_1.getConnections)())
|
|
15
|
-
applyBlock(socket, ip);
|
|
16
|
-
});
|
|
17
|
-
let blockFunctions = []; // "compiled" versions of the rules in config.block
|
|
18
|
-
function compileBlock(rules) {
|
|
19
|
-
blockFunctions = !Array.isArray(rules) ? []
|
|
20
|
-
: (0, misc_1.onlyTruthy)(rules.map(rule => !rule ? null
|
|
21
|
-
: (0, misc_1.with_)(rule.ip, ip => typeof ip !== 'string' ? null
|
|
22
|
-
: ip.includes('/') ? x => cidr_tools_1.default.contains(ip, x)
|
|
23
|
-
: ip.includes('*') ? (0, misc_1.with_)(ipMask2regExp(ip), re => x => re.test(x))
|
|
24
|
-
: x => x === ip)));
|
|
25
|
-
function ipMask2regExp(ipMask) {
|
|
26
|
-
return new RegExp(lodash_1.default.escapeRegExp(ipMask).replace(/\\\*/g, '.*'));
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
function applyBlock(socket, ip = (0, connections_1.normalizeIp)(socket.remoteAddress || '')) {
|
|
30
|
-
if (ip && blockFunctions.find(rule => rule(ip)))
|
|
31
|
-
return socket.destroy();
|
|
32
|
-
}
|
|
33
|
-
exports.applyBlock = applyBlock;
|
package/src/commands.js
DELETED
|
@@ -1,124 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
const perm_1 = require("./perm");
|
|
7
|
-
const config_1 = require("./config");
|
|
8
|
-
const lodash_1 = __importDefault(require("lodash"));
|
|
9
|
-
const update_1 = require("./update");
|
|
10
|
-
const listen_1 = require("./listen");
|
|
11
|
-
const yaml_1 = __importDefault(require("yaml"));
|
|
12
|
-
const const_1 = require("./const");
|
|
13
|
-
const readline_1 = require("readline");
|
|
14
|
-
try {
|
|
15
|
-
/*
|
|
16
|
-
is this try-block useful in case the stdin is unavailable?
|
|
17
|
-
Not sure, but someone reported a problem using nohup https://github.com/rejetto/hfs/issues/74
|
|
18
|
-
and I've found this example try-catching https://github.com/DefinitelyTyped/DefinitelyTyped/blob/dda83a906914489e09ca28afea12948529015d4a/types/node/readline.d.ts#L489
|
|
19
|
-
*/
|
|
20
|
-
(0, readline_1.createInterface)({ input: process.stdin }).on('line', parseCommandLine);
|
|
21
|
-
console.log(`HINT: type "help" for help`);
|
|
22
|
-
}
|
|
23
|
-
catch (_a) {
|
|
24
|
-
console.log("console commands not available");
|
|
25
|
-
}
|
|
26
|
-
function parseCommandLine(line) {
|
|
27
|
-
if (!line)
|
|
28
|
-
return;
|
|
29
|
-
const [name, ...params] = line.trim().split(/ +/);
|
|
30
|
-
const cmd = commands[name];
|
|
31
|
-
if (!cmd)
|
|
32
|
-
return console.error("cannot understand entered command, try 'help'");
|
|
33
|
-
if (cmd.cb.length > params.length)
|
|
34
|
-
return console.error("insufficient parameters, expected: " + cmd.params);
|
|
35
|
-
cmd.cb(...params).then(() => console.log("+++ command executed"), (err) => {
|
|
36
|
-
if (typeof err === 'string')
|
|
37
|
-
console.error("command failed:", err);
|
|
38
|
-
else
|
|
39
|
-
throw err;
|
|
40
|
-
});
|
|
41
|
-
}
|
|
42
|
-
const commands = {
|
|
43
|
-
help: {
|
|
44
|
-
params: '',
|
|
45
|
-
async cb() {
|
|
46
|
-
console.log("supported commands:", ...lodash_1.default.map(commands, ({ params }, name) => '\n - ' + name + ' ' + params));
|
|
47
|
-
}
|
|
48
|
-
},
|
|
49
|
-
'show-admin': {
|
|
50
|
-
params: '',
|
|
51
|
-
cb() {
|
|
52
|
-
(0, listen_1.openAdmin)();
|
|
53
|
-
}
|
|
54
|
-
},
|
|
55
|
-
'create-admin': {
|
|
56
|
-
params: '<password> [<username>=admin]',
|
|
57
|
-
async cb(password, username = 'admin') {
|
|
58
|
-
if ((0, perm_1.getAccount)(username))
|
|
59
|
-
throw `user ${username} already exists`;
|
|
60
|
-
const acc = (0, perm_1.addAccount)(username, { admin: true });
|
|
61
|
-
await (0, perm_1.updateAccount)(acc, acc => {
|
|
62
|
-
acc.password = password;
|
|
63
|
-
});
|
|
64
|
-
}
|
|
65
|
-
},
|
|
66
|
-
'change-password': {
|
|
67
|
-
params: '<user> <password>',
|
|
68
|
-
async cb(user, password) {
|
|
69
|
-
const acc = (0, perm_1.getAccount)(user);
|
|
70
|
-
if (!acc)
|
|
71
|
-
throw "user doesn't exist";
|
|
72
|
-
await (0, perm_1.updateAccount)(acc, acc => {
|
|
73
|
-
acc.password = password;
|
|
74
|
-
});
|
|
75
|
-
}
|
|
76
|
-
},
|
|
77
|
-
config: {
|
|
78
|
-
params: '<key> <value>',
|
|
79
|
-
async cb(key, value) {
|
|
80
|
-
const conf = (0, config_1.getConfigDefinition)(key);
|
|
81
|
-
if (!conf)
|
|
82
|
-
throw "specified key doesn't exist";
|
|
83
|
-
let v = value;
|
|
84
|
-
try {
|
|
85
|
-
v = JSON.parse(v);
|
|
86
|
-
}
|
|
87
|
-
catch (_a) { }
|
|
88
|
-
(0, config_1.setConfig)({ [key]: v });
|
|
89
|
-
}
|
|
90
|
-
},
|
|
91
|
-
'show-config': {
|
|
92
|
-
params: '<key>',
|
|
93
|
-
async cb(key) {
|
|
94
|
-
const conf = (0, config_1.getConfigDefinition)(key);
|
|
95
|
-
if (!conf)
|
|
96
|
-
throw "specified key doesn't exist";
|
|
97
|
-
console.log(yaml_1.default.stringify((0, config_1.getConfig)(key), { lineWidth: 1000 }).trim());
|
|
98
|
-
}
|
|
99
|
-
},
|
|
100
|
-
quit: {
|
|
101
|
-
params: '',
|
|
102
|
-
async cb() {
|
|
103
|
-
process.exit(0);
|
|
104
|
-
}
|
|
105
|
-
},
|
|
106
|
-
update: {
|
|
107
|
-
params: '',
|
|
108
|
-
cb: update_1.update
|
|
109
|
-
},
|
|
110
|
-
'check-update': {
|
|
111
|
-
params: '',
|
|
112
|
-
async cb() {
|
|
113
|
-
const update = await (0, update_1.getUpdate)();
|
|
114
|
-
console.log("new version available", update.name);
|
|
115
|
-
}
|
|
116
|
-
},
|
|
117
|
-
version: {
|
|
118
|
-
params: '',
|
|
119
|
-
async cb() {
|
|
120
|
-
console.log(const_1.VERSION);
|
|
121
|
-
console.log(const_1.BUILD_TIMESTAMP);
|
|
122
|
-
}
|
|
123
|
-
},
|
|
124
|
-
};
|
package/src/config.js
DELETED
|
@@ -1,168 +0,0 @@
|
|
|
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.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
|
-
});
|
package/src/connections.js
DELETED
|
@@ -1,57 +0,0 @@
|
|
|
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.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;
|