hfs 0.45.0 → 0.46.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -16
- package/admin/assets/index-963c15a5.js +517 -0
- package/{frontend/assets/sha512-b32d8af7.js → admin/assets/sha512-471fbd54.js} +2 -2
- package/admin/index.html +1 -1
- package/frontend/assets/index-c0f02a72.js +94 -0
- package/frontend/assets/index-f2f8fd6b.css +1 -0
- package/{admin/assets/sha512-c0389c0d.js → frontend/assets/sha512-7dcc825b.js} +2 -2
- package/frontend/fontello.css +1 -1
- package/frontend/index.html +2 -3
- package/package.json +4 -3
- package/plugins/download-counter/plugin.js +3 -1
- package/plugins/download-counter/public/main.js +3 -2
- package/plugins/list-uploader/plugin.js +21 -0
- package/plugins/list-uploader/public/main.js +27 -0
- package/plugins/vhosting/plugin.js +6 -3
- package/src/adminApis.js +9 -3
- package/src/api.auth.js +4 -4
- package/src/api.file_list.js +4 -5
- package/src/api.monitor.js +7 -5
- package/src/api.plugins.js +57 -13
- package/src/api.vfs.js +2 -1
- package/src/apiMiddleware.js +4 -0
- package/src/commands.js +11 -0
- package/src/config.js +9 -5
- package/src/connections.js +1 -1
- package/src/const.js +4 -3
- package/src/frontEndApis.js +33 -1
- package/src/github.js +65 -33
- package/src/index.js +4 -4
- package/src/lang.js +3 -2
- package/src/langs/embedded.js +2 -1
- package/src/langs/hfs-lang-fr.json +16 -4
- package/src/langs/hfs-lang-it.json +11 -4
- package/src/langs/hfs-lang-ru.json +16 -4
- package/src/langs/hfs-lang-vi.json +116 -0
- package/src/log.js +50 -47
- package/src/middlewares.js +10 -3
- package/src/plugins.js +181 -98
- package/src/serveFile.js +13 -5
- package/src/serveGuiFiles.js +8 -10
- package/src/throttler.js +11 -6
- package/src/update.js +1 -4
- package/src/upload.js +26 -9
- package/src/util-files.js +25 -6
- package/src/vfs.js +6 -12
- package/src/zip.js +2 -2
- package/admin/assets/index-101f19c9.js +0 -510
- package/frontend/assets/index-59572489.css +0 -1
- package/frontend/assets/index-5aeff3af.js +0 -94
package/src/log.js
CHANGED
|
@@ -27,7 +27,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
27
27
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
28
28
|
};
|
|
29
29
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
30
|
-
exports.
|
|
30
|
+
exports.logMw = exports.loggers = void 0;
|
|
31
31
|
const stream_1 = require("stream");
|
|
32
32
|
const config_1 = require("./config");
|
|
33
33
|
const fs_1 = require("fs");
|
|
@@ -38,6 +38,7 @@ const lodash_1 = __importDefault(require("lodash"));
|
|
|
38
38
|
const util_files_1 = require("./util-files");
|
|
39
39
|
const perm_1 = require("./perm");
|
|
40
40
|
const misc_1 = require("./misc");
|
|
41
|
+
const events_1 = __importDefault(require("./events"));
|
|
41
42
|
class Logger {
|
|
42
43
|
constructor(name) {
|
|
43
44
|
this.name = name;
|
|
@@ -80,54 +81,56 @@ errorLogFile.sub(path => {
|
|
|
80
81
|
});
|
|
81
82
|
const logRotation = (0, config_1.defineConfig)('log_rotation', 'weekly');
|
|
82
83
|
const dontLogNet = (0, config_1.defineConfig)('dont_log_net', '127.0.0.1|::1', v => (0, misc_1.makeNetMatcher)(v));
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
catch (e) { // ok, rename failed, but this doesn't mean we ain't gonna log
|
|
112
|
-
console.error(e);
|
|
113
|
-
}
|
|
114
|
-
stream = logger.reopen(); // keep variable updated
|
|
115
|
-
if (!stream)
|
|
116
|
-
return;
|
|
84
|
+
const debounce = lodash_1.default.debounce(cb => cb(), 1000);
|
|
85
|
+
const logMw = async (ctx, next) => {
|
|
86
|
+
const now = new Date();
|
|
87
|
+
await next();
|
|
88
|
+
console.debug(ctx.status, ctx.method, ctx.originalUrl);
|
|
89
|
+
Promise.race([(0, stream_1.once)(ctx.res, 'finish'), (0, stream_1.once)(ctx.res, 'close')]).then(() => {
|
|
90
|
+
var _a, _b, _c, _d, _e;
|
|
91
|
+
if (ctx.state.dont_log)
|
|
92
|
+
return;
|
|
93
|
+
if (dontLogNet.compiled()(ctx.ip))
|
|
94
|
+
return;
|
|
95
|
+
const isError = ctx.status >= 400;
|
|
96
|
+
const logger = isError && accessErrorLog || accessLogger;
|
|
97
|
+
const rotate = (_a = logRotation.get()) === null || _a === void 0 ? void 0 : _a[0];
|
|
98
|
+
let { stream, last, path } = logger;
|
|
99
|
+
if (!stream)
|
|
100
|
+
return;
|
|
101
|
+
logger.last = now;
|
|
102
|
+
if (rotate && last) { // rotation enabled and a file exists?
|
|
103
|
+
const passed = Number(now) - Number(last)
|
|
104
|
+
- 3600000; // be pessimistic and count a possible DST change
|
|
105
|
+
if (rotate === 'm' && (passed >= 31 * const_1.DAY || now.getMonth() !== last.getMonth())
|
|
106
|
+
|| rotate === 'd' && (passed >= const_1.DAY || now.getDate() !== last.getDate()) // checking passed will solve the case when the day of the month is the same but a month has passed
|
|
107
|
+
|| rotate === 'w' && (passed >= 7 * const_1.DAY || now.getDay() < last.getDay())) {
|
|
108
|
+
stream.end();
|
|
109
|
+
const postfix = last.getFullYear() + '-' + doubleDigit(last.getMonth() + 1) + '-' + doubleDigit(last.getDate());
|
|
110
|
+
try { // other logging requests shouldn't happen while we are renaming. Since this is very infrequent we can tolerate solving this by making it sync.
|
|
111
|
+
(0, fs_1.renameSync)(path, path + '-' + postfix);
|
|
117
112
|
}
|
|
113
|
+
catch (e) { // ok, rename failed, but this doesn't mean we ain't gonna log
|
|
114
|
+
console.error(e);
|
|
115
|
+
}
|
|
116
|
+
stream = logger.reopen(); // keep variable updated
|
|
117
|
+
if (!stream)
|
|
118
|
+
return;
|
|
118
119
|
}
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
120
|
+
}
|
|
121
|
+
const format = '%s - %s [%s] "%s %s HTTP/%s" %d %s\n'; // Apache's Common Log Format
|
|
122
|
+
const a = now.toString().split(' ');
|
|
123
|
+
const date = a[2] + '/' + a[1] + '/' + a[3] + ':' + a[4] + ' ' + ((_b = a[5]) === null || _b === void 0 ? void 0 : _b.slice(3));
|
|
124
|
+
const user = (0, perm_1.getCurrentUsername)(ctx);
|
|
125
|
+
const length = (_c = ctx.state.length) !== null && _c !== void 0 ? _c : ctx.length;
|
|
126
|
+
const uri = (_d = ctx.originalUrl) === null || _d === void 0 ? void 0 : _d.replace(/&?csrf=[^&]+/, ''); // temporary workaround. To avoid csrf in url we need to stop using EventSource.
|
|
127
|
+
events_1.default.emit(logger.name, Object.assign(lodash_1.default.pick(ctx, ['ip', 'method', 'status']), { length, user, ts: now, uri }));
|
|
128
|
+
debounce(() => // once in a while we check if the file is still good (not deleted, etc), or we'll reopen it
|
|
129
|
+
(0, promises_1.stat)(logger.path).catch(() => logger.reopen())); // async = smoother but we may lose some entries
|
|
130
|
+
stream.write(util.format(format, ctx.ip, user || '-', date, ctx.method, uri, ctx.req.httpVersion, ctx.status, (_e = length === null || length === void 0 ? void 0 : length.toString()) !== null && _e !== void 0 ? _e : '-'));
|
|
131
|
+
});
|
|
132
|
+
};
|
|
133
|
+
exports.logMw = logMw;
|
|
131
134
|
function doubleDigit(n) {
|
|
132
135
|
return n > 9 ? n : '0' + n;
|
|
133
136
|
}
|
package/src/middlewares.js
CHANGED
|
@@ -4,7 +4,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
5
|
};
|
|
6
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
-
exports.paramsDecoder = exports.prepareState = exports.getProxyDetected = exports.someSecurity = exports.sendErrorPage = exports.serveGuiAndSharedFiles = exports.headRequests = exports.gzipper = exports.sessionDuration = void 0;
|
|
7
|
+
exports.paramsDecoder = exports.prepareState = exports.getProxyDetected = exports.someSecurity = exports.baseUrl = exports.sendErrorPage = exports.serveGuiAndSharedFiles = exports.headRequests = exports.gzipper = exports.sessionDuration = void 0;
|
|
8
8
|
const koa_compress_1 = __importDefault(require("koa-compress"));
|
|
9
9
|
const const_1 = require("./const");
|
|
10
10
|
const const_2 = require("./const");
|
|
@@ -159,13 +159,13 @@ const errorMessages = {
|
|
|
159
159
|
[const_1.HTTP_UNAUTHORIZED]: "Unauthorized",
|
|
160
160
|
[const_1.HTTP_FORBIDDEN]: "Forbidden",
|
|
161
161
|
};
|
|
162
|
-
|
|
162
|
+
exports.baseUrl = (0, config_1.defineConfig)('base_url', '');
|
|
163
163
|
async function sendFolderList(node, ctx) {
|
|
164
164
|
let { depth = 0, folders, prepend } = ctx.query;
|
|
165
165
|
ctx.type = 'text';
|
|
166
166
|
if (prepend === undefined || prepend === '*') { // * = force auto-detection even if we have baseUrl set
|
|
167
167
|
const { URL } = ctx;
|
|
168
|
-
const base = prepend === undefined && baseUrl.get()
|
|
168
|
+
const base = prepend === undefined && exports.baseUrl.get()
|
|
169
169
|
|| URL.protocol + '//' + URL.host + ctx.state.revProxyPath;
|
|
170
170
|
prepend = base + ctx.originalUrl.split('?')[0];
|
|
171
171
|
}
|
|
@@ -179,6 +179,13 @@ async function sendFolderList(node, ctx) {
|
|
|
179
179
|
let proxyDetected;
|
|
180
180
|
const someSecurity = async (ctx, next) => {
|
|
181
181
|
ctx.request.ip = (0, connections_1.normalizeIp)(ctx.ip);
|
|
182
|
+
// don't allow sessions to change ip
|
|
183
|
+
const ss = ctx.session;
|
|
184
|
+
if (ss === null || ss === void 0 ? void 0 : ss.username)
|
|
185
|
+
if (!ss.ip)
|
|
186
|
+
ss.ip = ctx.ip;
|
|
187
|
+
else if (ss.ip !== ctx.ip)
|
|
188
|
+
delete ss.username;
|
|
182
189
|
try {
|
|
183
190
|
if ((0, misc_1.dirTraversal)(decodeURI(ctx.path)))
|
|
184
191
|
return ctx.status = const_1.HTTP_FOOL;
|
package/src/plugins.js
CHANGED
|
@@ -27,7 +27,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
27
27
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
28
28
|
};
|
|
29
29
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
30
|
-
exports.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;
|
|
30
|
+
exports.parsePluginSource = exports.rescan = exports.pluginsConfig = exports.enablePlugins = exports.pluginsWatcher = exports.getAvailablePlugins = exports.Plugin = exports.pluginsMiddleware = exports.getPluginConfigFields = exports.findPluginByRepo = exports.mapPlugins = exports.getPluginInfo = exports.setPluginConfig = exports.startPlugin = exports.stopPlugin = exports.enablePlugin = exports.isPluginEnabled = exports.isPluginRunning = exports.STORAGE_FOLDER = exports.DISABLING_POSTFIX = exports.PATH = void 0;
|
|
31
31
|
const fast_glob_1 = __importDefault(require("fast-glob"));
|
|
32
32
|
const watchLoad_1 = require("./watchLoad");
|
|
33
33
|
const lodash_1 = __importDefault(require("lodash"));
|
|
@@ -44,18 +44,48 @@ const path_1 = require("path");
|
|
|
44
44
|
const customHtml_1 = require("./customHtml");
|
|
45
45
|
exports.PATH = 'plugins';
|
|
46
46
|
exports.DISABLING_POSTFIX = '-disabled';
|
|
47
|
+
exports.STORAGE_FOLDER = 'storage';
|
|
47
48
|
const plugins = {};
|
|
48
49
|
function isPluginRunning(id) {
|
|
49
50
|
var _a;
|
|
50
|
-
return (_a = plugins[id]) === null || _a === void 0 ? void 0 : _a.started;
|
|
51
|
+
return Boolean((_a = plugins[id]) === null || _a === void 0 ? void 0 : _a.started);
|
|
51
52
|
}
|
|
52
53
|
exports.isPluginRunning = isPluginRunning;
|
|
54
|
+
function isPluginEnabled(id) {
|
|
55
|
+
return exports.enablePlugins.get().includes(id);
|
|
56
|
+
}
|
|
57
|
+
exports.isPluginEnabled = isPluginEnabled;
|
|
53
58
|
function enablePlugin(id, state = true) {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
59
|
+
if (state && !getPluginInfo(id))
|
|
60
|
+
throw Error('miss');
|
|
61
|
+
exports.enablePlugins.set(arr => {
|
|
62
|
+
if (arr.includes(id) === state)
|
|
63
|
+
return arr;
|
|
64
|
+
console.log("switching plugin", id, state ? "on" : "off");
|
|
65
|
+
return arr.includes(id) === state ? arr
|
|
66
|
+
: state ? [...arr, id]
|
|
67
|
+
: arr.filter((x) => x !== id);
|
|
68
|
+
});
|
|
57
69
|
}
|
|
58
70
|
exports.enablePlugin = enablePlugin;
|
|
71
|
+
async function stopPlugin(id) {
|
|
72
|
+
enablePlugin(id, false);
|
|
73
|
+
await waitRunning(id, false);
|
|
74
|
+
}
|
|
75
|
+
exports.stopPlugin = stopPlugin;
|
|
76
|
+
async function startPlugin(id) {
|
|
77
|
+
enablePlugin(id);
|
|
78
|
+
await waitRunning(id);
|
|
79
|
+
}
|
|
80
|
+
exports.startPlugin = startPlugin;
|
|
81
|
+
async function waitRunning(id, state = true) {
|
|
82
|
+
while (isPluginRunning(id) !== state) {
|
|
83
|
+
await (0, misc_1.wait)(500);
|
|
84
|
+
const error = getError(id);
|
|
85
|
+
if (error)
|
|
86
|
+
throw Error(error);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
59
89
|
// nullish values are equivalent to defaultValues
|
|
60
90
|
function setPluginConfig(id, changes) {
|
|
61
91
|
exports.pluginsConfig.set(allConfigs => {
|
|
@@ -83,53 +113,56 @@ function mapPlugins(cb) {
|
|
|
83
113
|
}).filter(x => x !== undefined);
|
|
84
114
|
}
|
|
85
115
|
exports.mapPlugins = mapPlugins;
|
|
116
|
+
function findPluginByRepo(repo) {
|
|
117
|
+
return lodash_1.default.find(plugins, pl => { var _a; return ((_a = pl.getData()) === null || _a === void 0 ? void 0 : _a.repo) === repo; })
|
|
118
|
+
|| lodash_1.default.find(availablePlugins, { repo });
|
|
119
|
+
}
|
|
120
|
+
exports.findPluginByRepo = findPluginByRepo;
|
|
86
121
|
function getPluginConfigFields(id) {
|
|
87
122
|
var _a;
|
|
88
123
|
return (_a = plugins[id]) === null || _a === void 0 ? void 0 : _a.getData().config;
|
|
89
124
|
}
|
|
90
125
|
exports.getPluginConfigFields = getPluginConfigFields;
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
after[id] = res;
|
|
103
|
-
}
|
|
104
|
-
catch (e) {
|
|
105
|
-
printError(id, e);
|
|
106
|
-
}
|
|
107
|
-
// expose public plugins' files
|
|
108
|
-
const { path } = ctx;
|
|
109
|
-
if (!ctx.pluginStopped) {
|
|
110
|
-
if (path.startsWith(const_1.PLUGINS_PUB_URI)) {
|
|
111
|
-
const a = path.substring(const_1.PLUGINS_PUB_URI.length).split('/');
|
|
112
|
-
const name = a.shift();
|
|
113
|
-
if (plugins.hasOwnProperty(name)) // do it only if the plugin is loaded
|
|
114
|
-
await (0, serveFile_1.serveFile)(ctx, plugins[name].folder + '/public/' + a.join('/'), 'auto');
|
|
115
|
-
return;
|
|
116
|
-
}
|
|
117
|
-
await next();
|
|
126
|
+
const pluginsMiddleware = async (ctx, next) => {
|
|
127
|
+
var _a;
|
|
128
|
+
const after = {};
|
|
129
|
+
// run middleware plugins
|
|
130
|
+
for (const [id, pl] of Object.entries(plugins))
|
|
131
|
+
try {
|
|
132
|
+
const res = await ((_a = pl.middleware) === null || _a === void 0 ? void 0 : _a.call(pl, ctx));
|
|
133
|
+
if (res === true)
|
|
134
|
+
ctx.pluginStopped = true;
|
|
135
|
+
if (typeof res === 'function')
|
|
136
|
+
after[id] = res;
|
|
118
137
|
}
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
138
|
+
catch (e) {
|
|
139
|
+
printError(id, e);
|
|
140
|
+
}
|
|
141
|
+
// expose public plugins' files
|
|
142
|
+
const { path } = ctx;
|
|
143
|
+
if (!ctx.pluginStopped) {
|
|
144
|
+
if (path.startsWith(const_1.PLUGINS_PUB_URI)) {
|
|
145
|
+
const a = path.substring(const_1.PLUGINS_PUB_URI.length).split('/');
|
|
146
|
+
const name = a.shift();
|
|
147
|
+
if (plugins.hasOwnProperty(name)) // do it only if the plugin is loaded
|
|
148
|
+
await (0, serveFile_1.serveFile)(ctx, plugins[name].folder + '/public/' + a.join('/'), 'auto');
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
await next();
|
|
130
152
|
}
|
|
131
|
-
|
|
153
|
+
for (const [id, f] of Object.entries(after))
|
|
154
|
+
try {
|
|
155
|
+
await f();
|
|
156
|
+
}
|
|
157
|
+
catch (e) {
|
|
158
|
+
printError(id, e);
|
|
159
|
+
}
|
|
160
|
+
};
|
|
132
161
|
exports.pluginsMiddleware = pluginsMiddleware;
|
|
162
|
+
function printError(id, e) {
|
|
163
|
+
console.log(`error middleware plugin ${id}: ${(e === null || e === void 0 ? void 0 : e.message) || e}`);
|
|
164
|
+
console.debug(e);
|
|
165
|
+
}
|
|
133
166
|
class Plugin {
|
|
134
167
|
constructor(id, folder, data, unwatch) {
|
|
135
168
|
this.id = id;
|
|
@@ -139,9 +172,6 @@ class Plugin {
|
|
|
139
172
|
this.started = new Date();
|
|
140
173
|
if (!data)
|
|
141
174
|
throw 'invalid data';
|
|
142
|
-
if (plugins[id])
|
|
143
|
-
throw "unload first: " + id;
|
|
144
|
-
plugins[id] = this;
|
|
145
175
|
this.data = data = { ...data }; // clone to make object modifiable. Objects coming from import are not.
|
|
146
176
|
// some validation
|
|
147
177
|
for (const k of ['frontend_css', 'frontend_js']) {
|
|
@@ -154,6 +184,10 @@ class Plugin {
|
|
|
154
184
|
}
|
|
155
185
|
}
|
|
156
186
|
}
|
|
187
|
+
get version() {
|
|
188
|
+
var _a;
|
|
189
|
+
return (_a = this.data) === null || _a === void 0 ? void 0 : _a.version;
|
|
190
|
+
}
|
|
157
191
|
get middleware() {
|
|
158
192
|
var _a;
|
|
159
193
|
return (_a = this.data) === null || _a === void 0 ? void 0 : _a.middleware;
|
|
@@ -175,6 +209,9 @@ class Plugin {
|
|
|
175
209
|
}
|
|
176
210
|
async unload(reloading = false) {
|
|
177
211
|
var _a, _b;
|
|
212
|
+
if (!this.started)
|
|
213
|
+
return;
|
|
214
|
+
this.started = null;
|
|
178
215
|
const { id } = this;
|
|
179
216
|
try {
|
|
180
217
|
await ((_b = (_a = this.data) === null || _a === void 0 ? void 0 : _a.unload) === null || _b === void 0 ? void 0 : _b.call(_a));
|
|
@@ -184,14 +221,9 @@ class Plugin {
|
|
|
184
221
|
catch (e) {
|
|
185
222
|
console.log('error unloading plugin', id, String(e));
|
|
186
223
|
}
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
return;
|
|
224
|
+
if (this.data)
|
|
225
|
+
this.data.unload = undefined;
|
|
190
226
|
this.unwatch();
|
|
191
|
-
if (availablePlugins[id])
|
|
192
|
-
events_1.default.emit('pluginStopped', availablePlugins[id]);
|
|
193
|
-
else
|
|
194
|
-
events_1.default.emit('pluginUninstalled', id);
|
|
195
227
|
}
|
|
196
228
|
}
|
|
197
229
|
exports.Plugin = Plugin;
|
|
@@ -210,57 +242,86 @@ exports.pluginsWatcher = (0, misc_1.watchDir)(exports.PATH, rescanAsap);
|
|
|
210
242
|
exports.enablePlugins = (0, config_1.defineConfig)('enable_plugins', ['antibrute']);
|
|
211
243
|
exports.enablePlugins.sub(rescanAsap);
|
|
212
244
|
exports.pluginsConfig = (0, config_1.defineConfig)('plugins_config', {});
|
|
245
|
+
const pluginWatchers = new Map();
|
|
213
246
|
async function rescan() {
|
|
214
247
|
console.debug('scanning plugins');
|
|
215
|
-
const
|
|
216
|
-
const foundDisabled = {};
|
|
217
|
-
const MASK = exports.PATH + '/*/plugin.js'; // be sure to not use path.join as fast-glob doesn't work with \
|
|
218
|
-
const pluginSources = [MASK];
|
|
248
|
+
const patterns = [exports.PATH + '/*'];
|
|
219
249
|
if (const_1.APP_PATH !== process.cwd())
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
if (
|
|
224
|
-
continue;
|
|
225
|
-
if (!exports.enablePlugins.get().includes(id)) {
|
|
226
|
-
try {
|
|
227
|
-
const source = await (0, promises_1.readFile)(f, 'utf8');
|
|
228
|
-
foundDisabled[id] = parsePluginSource(id, source);
|
|
229
|
-
}
|
|
230
|
-
catch (_a) { }
|
|
250
|
+
patterns.push((0, misc_1.adjustStaticPathForGlob)(const_1.APP_PATH) + '/' + patterns[0]);
|
|
251
|
+
const met = [];
|
|
252
|
+
for (const { path, dirent } of await (0, fast_glob_1.default)(patterns, { onlyFiles: false, suppressErrors: true, objectMode: true })) {
|
|
253
|
+
if (!dirent.isDirectory() || path.endsWith(exports.DISABLING_POSTFIX))
|
|
231
254
|
continue;
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
if (!plugins[id]) // already loaded
|
|
237
|
-
loadPlugin(id, f);
|
|
238
|
-
}
|
|
239
|
-
for (const [id, p] of Object.entries(foundDisabled)) {
|
|
240
|
-
const a = availablePlugins[id];
|
|
241
|
-
if ((0, misc_1.same)(a, p))
|
|
255
|
+
const id = path.split('/').slice(-1)[0];
|
|
256
|
+
met.push(id);
|
|
257
|
+
const w = pluginWatchers.get(id);
|
|
258
|
+
if (w)
|
|
242
259
|
continue;
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
events_1.default.emit('pluginUpdated', p);
|
|
246
|
-
else if (!plugins[id])
|
|
247
|
-
events_1.default.emit('pluginInstalled', p);
|
|
260
|
+
console.debug('plugin watch', id);
|
|
261
|
+
pluginWatchers.set(id, watchPlugin(id, (0, path_1.join)(path, 'plugin.js')));
|
|
248
262
|
}
|
|
249
|
-
for (const id
|
|
250
|
-
if (!
|
|
251
|
-
|
|
252
|
-
|
|
263
|
+
for (const [id, cancelWatcher] of pluginWatchers.entries())
|
|
264
|
+
if (!met.includes(id)) {
|
|
265
|
+
enablePlugin(id, false);
|
|
266
|
+
console.debug('plugin unwatch', id);
|
|
267
|
+
cancelWatcher();
|
|
268
|
+
pluginWatchers.delete(id);
|
|
253
269
|
}
|
|
254
|
-
for (const [id, p] of Object.entries(plugins))
|
|
255
|
-
if (!found.includes(id))
|
|
256
|
-
await p.unload();
|
|
257
270
|
}
|
|
258
271
|
exports.rescan = rescan;
|
|
259
|
-
function
|
|
272
|
+
function watchPlugin(id, path) {
|
|
260
273
|
const module = (0, path_1.resolve)(path);
|
|
261
|
-
|
|
274
|
+
let starting;
|
|
275
|
+
exports.enablePlugins.sub(() => {
|
|
276
|
+
if (!getPluginInfo(id))
|
|
277
|
+
return; // not loaded yet
|
|
278
|
+
const enabled = isPluginEnabled(id);
|
|
279
|
+
if (enabled !== isPluginRunning(id))
|
|
280
|
+
return enabled ? start() : stop();
|
|
281
|
+
});
|
|
282
|
+
const { unwatch } = (0, watchLoad_1.watchLoad)(module, async (source) => {
|
|
283
|
+
const notRunning = availablePlugins[id];
|
|
284
|
+
if (!source)
|
|
285
|
+
return onUninstalled();
|
|
286
|
+
if (isPluginEnabled(id))
|
|
287
|
+
return start();
|
|
288
|
+
const p = parsePluginSource(id, source);
|
|
289
|
+
if ((0, misc_1.same)(notRunning, p))
|
|
290
|
+
return;
|
|
291
|
+
availablePlugins[id] = p;
|
|
292
|
+
events_1.default.emit(notRunning ? 'pluginUpdated' : 'pluginInstalled', p);
|
|
293
|
+
});
|
|
294
|
+
return () => {
|
|
295
|
+
unwatch();
|
|
296
|
+
return onUninstalled();
|
|
297
|
+
};
|
|
298
|
+
async function onUninstalled() {
|
|
299
|
+
await stop();
|
|
300
|
+
delete availablePlugins[id];
|
|
301
|
+
events_1.default.emit('pluginUninstalled', id);
|
|
302
|
+
}
|
|
303
|
+
async function markItAvailable() {
|
|
304
|
+
delete plugins[id];
|
|
305
|
+
const source = await (0, promises_1.readFile)(module, 'utf8');
|
|
306
|
+
availablePlugins[id] = parsePluginSource(id, source);
|
|
307
|
+
}
|
|
308
|
+
async function stop() {
|
|
309
|
+
await starting;
|
|
310
|
+
const p = plugins[id];
|
|
311
|
+
if (!p)
|
|
312
|
+
return;
|
|
313
|
+
await p.unload();
|
|
314
|
+
await markItAvailable();
|
|
315
|
+
events_1.default.emit('pluginStopped', p);
|
|
316
|
+
}
|
|
317
|
+
async function start() {
|
|
262
318
|
var _a;
|
|
319
|
+
if (starting)
|
|
320
|
+
return;
|
|
263
321
|
try {
|
|
322
|
+
starting = (0, misc_1.pendingPromise)();
|
|
323
|
+
if (getPluginInfo(id))
|
|
324
|
+
setError(id, '');
|
|
264
325
|
const alreadyRunning = plugins[id];
|
|
265
326
|
console.log(alreadyRunning ? "reloading plugin" : "loading plugin", id);
|
|
266
327
|
const { init, ...data } = await (_a = module, Promise.resolve().then(() => __importStar(require(_a))));
|
|
@@ -271,14 +332,17 @@ function loadPlugin(id, path) {
|
|
|
271
332
|
console.log("plugin", id, data.badApi);
|
|
272
333
|
await (alreadyRunning === null || alreadyRunning === void 0 ? void 0 : alreadyRunning.unload(true));
|
|
273
334
|
console.debug("starting plugin", id);
|
|
335
|
+
const storageDir = (0, path_1.resolve)(module, '..', exports.STORAGE_FOLDER) + (const_1.IS_WINDOWS ? '\\' : '/');
|
|
336
|
+
await (0, promises_1.mkdir)(storageDir, { recursive: true });
|
|
274
337
|
const res = await (init === null || init === void 0 ? void 0 : init.call(null, {
|
|
275
338
|
srcDir: __dirname,
|
|
339
|
+
storageDir,
|
|
276
340
|
const: Const,
|
|
277
341
|
require,
|
|
278
342
|
getConnections: connections_1.getConnections,
|
|
279
343
|
events: events_1.default,
|
|
280
344
|
log(...args) {
|
|
281
|
-
console.log('plugin', id
|
|
345
|
+
console.log('plugin', id + ':', ...args);
|
|
282
346
|
},
|
|
283
347
|
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; },
|
|
284
348
|
setConfig: (cfgKey, value) => setPluginConfig(id, { [cfgKey]: value }),
|
|
@@ -298,13 +362,16 @@ function loadPlugin(id, path) {
|
|
|
298
362
|
});
|
|
299
363
|
},
|
|
300
364
|
getHfsConfig: config_1.getConfig,
|
|
365
|
+
customApiCall(method, params) {
|
|
366
|
+
return mapPlugins(pl => { var _a, _b; return (_b = (_a = pl.getData().customApi) === null || _a === void 0 ? void 0 : _a[method]) === null || _b === void 0 ? void 0 : _b.call(_a, params); });
|
|
367
|
+
}
|
|
301
368
|
}));
|
|
302
369
|
const folder = (0, path_1.dirname)(module);
|
|
303
370
|
Object.assign(data, res, {
|
|
304
371
|
customHtml: (0, customHtml_1.newCustomHtmlState)()
|
|
305
372
|
});
|
|
306
373
|
const customHtmlWatcher = (0, customHtml_1.watchLoadCustomHtml)(data.customHtml, folder);
|
|
307
|
-
const plugin = new Plugin(id, folder, data,
|
|
374
|
+
const plugin = plugins[id] = new Plugin(id, folder, data, customHtmlWatcher.unwatch);
|
|
308
375
|
if (alreadyRunning)
|
|
309
376
|
events_1.default.emit('pluginUpdated', Object.assign(lodash_1.default.pick(plugin, 'started'), getPluginInfo(id)));
|
|
310
377
|
else {
|
|
@@ -315,9 +382,23 @@ function loadPlugin(id, path) {
|
|
|
315
382
|
}
|
|
316
383
|
}
|
|
317
384
|
catch (e) {
|
|
318
|
-
|
|
385
|
+
await markItAvailable();
|
|
386
|
+
e = e.message || String(e);
|
|
387
|
+
console.log(`plugin error: ${id}:`, e);
|
|
388
|
+
setError(id, e);
|
|
319
389
|
}
|
|
320
|
-
|
|
390
|
+
finally {
|
|
391
|
+
starting === null || starting === void 0 ? void 0 : starting.resolve();
|
|
392
|
+
starting = undefined;
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
function getError(id) {
|
|
397
|
+
return getPluginInfo(id).error;
|
|
398
|
+
}
|
|
399
|
+
function setError(id, error) {
|
|
400
|
+
getPluginInfo(id).error = error;
|
|
401
|
+
events_1.default.emit('pluginUpdated', { id, error });
|
|
321
402
|
}
|
|
322
403
|
function deleteModule(id) {
|
|
323
404
|
var _a;
|
|
@@ -344,12 +425,14 @@ function deleteModule(id) {
|
|
|
344
425
|
}
|
|
345
426
|
(0, misc_1.onProcessExit)(() => Promise.allSettled(mapPlugins(pl => pl.unload())));
|
|
346
427
|
function parsePluginSource(id, source) {
|
|
347
|
-
var _a, _b, _c, _d, _e, _f;
|
|
428
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
348
429
|
const pl = { id };
|
|
349
430
|
pl.description = (0, misc_1.tryJson)((_a = /exports.description *= *(".*")/.exec(source)) === null || _a === void 0 ? void 0 : _a[1]);
|
|
350
431
|
pl.repo = (_b = /exports.repo *= *"(.*)"/.exec(source)) === null || _b === void 0 ? void 0 : _b[1];
|
|
351
432
|
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;
|
|
352
433
|
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;
|
|
434
|
+
pl.depend = (_h = (0, misc_1.tryJson)((_g = /exports.depend *= *(\[.*\])/m.exec(source)) === null || _g === void 0 ? void 0 : _g[1])) === null || _h === void 0 ? void 0 : _h.filter((x) => typeof x.repo === 'string' && x.version === undefined || typeof x.version === 'number'
|
|
435
|
+
|| console.warn("plugin dependency discarded", x));
|
|
353
436
|
if (Array.isArray(pl.apiRequired) && (pl.apiRequired.length !== 2 || !pl.apiRequired.every(lodash_1.default.isFinite))) // validate [from,to] form
|
|
354
437
|
pl.apiRequired = undefined;
|
|
355
438
|
calculateBadApi(pl);
|
package/src/serveFile.js
CHANGED
|
@@ -14,6 +14,7 @@ const misc_1 = require("./misc");
|
|
|
14
14
|
const lodash_1 = __importDefault(require("lodash"));
|
|
15
15
|
const path_1 = __importDefault(require("path"));
|
|
16
16
|
const util_1 = require("util");
|
|
17
|
+
const connections_1 = require("./connections");
|
|
17
18
|
const allowedReferer = (0, config_1.defineConfig)('allowed_referer', '');
|
|
18
19
|
function serveFileNode(ctx, node) {
|
|
19
20
|
var _a;
|
|
@@ -27,15 +28,15 @@ function serveFileNode(ctx, node) {
|
|
|
27
28
|
if (ref && ref !== host() // automatic accept if referer is basically the hosting domain
|
|
28
29
|
&& !(0, misc_1.matches)(ref, allowed))
|
|
29
30
|
return ctx.status = const_1.HTTP_FORBIDDEN;
|
|
30
|
-
function host() {
|
|
31
|
-
const s = ctx.get('host');
|
|
32
|
-
return s[0] === '[' ? s.slice(1, s.indexOf(']')) : s === null || s === void 0 ? void 0 : s.split(':')[0];
|
|
33
|
-
}
|
|
34
31
|
}
|
|
35
32
|
ctx.vfsNode = node; // useful to tell service files from files shared by the user
|
|
36
33
|
if ('dl' in ctx.query) // please, download
|
|
37
34
|
ctx.attachment(name);
|
|
38
35
|
return serveFile(ctx, source || '', mimeString);
|
|
36
|
+
function host() {
|
|
37
|
+
const s = ctx.get('host');
|
|
38
|
+
return s[0] === '[' ? s.slice(1, s.indexOf(']')) : s === null || s === void 0 ? void 0 : s.split(':')[0];
|
|
39
|
+
}
|
|
39
40
|
}
|
|
40
41
|
exports.serveFileNode = serveFileNode;
|
|
41
42
|
const mimeCfg = (0, config_1.defineConfig)('mime', { '*': 'auto' });
|
|
@@ -65,8 +66,15 @@ async function serveFile(ctx, source, mime, content) {
|
|
|
65
66
|
return ctx.status = const_1.HTTP_NOT_MODIFIED;
|
|
66
67
|
if (content !== undefined)
|
|
67
68
|
return ctx.body = content;
|
|
68
|
-
const
|
|
69
|
+
const { size } = stats;
|
|
70
|
+
const range = getRange(ctx, size);
|
|
69
71
|
ctx.body = (0, fs_1.createReadStream)(source, range);
|
|
72
|
+
if (ctx.vfsNode)
|
|
73
|
+
(0, connections_1.updateConnection)(ctx.state.connection, {
|
|
74
|
+
op: 'download',
|
|
75
|
+
opTotal: stats.size,
|
|
76
|
+
opOffset: range && (range.start / size),
|
|
77
|
+
});
|
|
70
78
|
}
|
|
71
79
|
catch (e) {
|
|
72
80
|
return ctx.status = const_1.HTTP_NOT_FOUND;
|