hfs 0.48.3 → 0.49.0-alpha2
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 +4 -2
- package/admin/assets/index-6afd4b06.js +542 -0
- package/{frontend/assets/sha512-70e9a0d9.js → admin/assets/sha512-f5f12dc1.js} +1 -1
- package/admin/index.html +1 -1
- package/central.json +30 -8
- package/frontend/assets/index-3a379840.js +94 -0
- package/frontend/assets/index-db5f0e4b.css +1 -0
- package/{admin/assets/sha512-2627feb7.js → frontend/assets/sha512-3392c31f.js} +1 -1
- package/frontend/index.html +2 -2
- package/package.json +1 -2
- package/plugins/download-counter/plugin.js +14 -15
- package/src/adminApis.js +1 -0
- package/src/api.auth.js +6 -6
- package/src/api.file_list.js +15 -8
- package/src/api.net.js +97 -72
- package/src/api.plugins.js +15 -26
- package/src/api.vfs.js +9 -12
- package/src/block.js +14 -1
- package/src/commands.js +1 -1
- package/src/comments.js +49 -0
- package/src/config.js +1 -5
- package/src/const.js +3 -2
- package/src/cross.js +43 -2
- package/src/customHtml.js +1 -1
- package/src/debounceAsync.js +18 -1
- package/src/frontEndApis.js +28 -6
- package/src/github.js +24 -30
- package/src/langs/hfs-lang-it.json +5 -3
- package/src/langs/hfs-lang-ru.json +6 -4
- package/src/listen.js +25 -15
- package/src/middlewares.js +3 -1
- package/src/misc.js +6 -5
- package/src/perm.js +19 -12
- package/src/plugins.js +62 -38
- package/src/serveFile.js +2 -3
- package/src/upload.js +5 -1
- package/src/util-files.js +38 -2
- package/src/util-http.js +8 -6
- package/src/vfs.js +24 -32
- package/src/watchLoad.js +5 -12
- package/src/zip.js +4 -4
- package/admin/assets/index-4e55b514.js +0 -536
- package/frontend/assets/index-cadcb0e9.css +0 -1
- package/frontend/assets/index-e24651da.js +0 -94
package/src/block.js
CHANGED
|
@@ -6,8 +6,12 @@ const config_1 = require("./config");
|
|
|
6
6
|
const connections_1 = require("./connections");
|
|
7
7
|
const misc_1 = require("./misc");
|
|
8
8
|
const block = (0, config_1.defineConfig)('block', [], rules => {
|
|
9
|
+
const now = new Date();
|
|
9
10
|
const ret = !Array.isArray(rules) ? []
|
|
10
|
-
: (0, misc_1.onlyTruthy)(rules.map(rule =>
|
|
11
|
+
: (0, misc_1.onlyTruthy)(rules.map(rule => {
|
|
12
|
+
rule.expire && (rule.expire = new Date(rule.expire));
|
|
13
|
+
return !(rule.expire > now) && (0, misc_1.makeNetMatcher)(rule.ip, true);
|
|
14
|
+
}));
|
|
11
15
|
// reapply new block to existing connections
|
|
12
16
|
for (const { socket, ip } of (0, connections_1.getConnections)())
|
|
13
17
|
applyBlock(socket, ip);
|
|
@@ -18,3 +22,12 @@ function applyBlock(socket, ip = (0, connections_1.normalizeIp)(socket.remoteAdd
|
|
|
18
22
|
return socket.destroy();
|
|
19
23
|
}
|
|
20
24
|
exports.applyBlock = applyBlock;
|
|
25
|
+
setInterval(() => {
|
|
26
|
+
const now = new Date();
|
|
27
|
+
const next = block.get().filter(x => !x.expire || x.expire > now);
|
|
28
|
+
const n = block.get().length - next.length;
|
|
29
|
+
if (!n)
|
|
30
|
+
return;
|
|
31
|
+
console.log("blocking rules:", n, "expired");
|
|
32
|
+
block.set(next);
|
|
33
|
+
}, misc_1.MINUTE / 2);
|
package/src/commands.js
CHANGED
package/src/comments.js
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
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.setCommentFor = exports.getCommentFor = exports.descriptIon = exports.DESCRIPT_ION = void 0;
|
|
7
|
+
const config_1 = require("./config");
|
|
8
|
+
const path_1 = require("path");
|
|
9
|
+
const cross_1 = require("./cross");
|
|
10
|
+
const util_files_1 = require("./util-files");
|
|
11
|
+
const promises_1 = require("fs/promises");
|
|
12
|
+
const misc_1 = require("./misc");
|
|
13
|
+
const lodash_1 = __importDefault(require("lodash"));
|
|
14
|
+
exports.DESCRIPT_ION = 'descript.ion';
|
|
15
|
+
exports.descriptIon = (0, config_1.defineConfig)('descript_ion', true);
|
|
16
|
+
async function getCommentFor(path) {
|
|
17
|
+
return !path || !exports.descriptIon.get() ? undefined
|
|
18
|
+
: readDescription((0, path_1.dirname)(path)).then(x => x.get((0, cross_1.basename)(path)), () => undefined);
|
|
19
|
+
}
|
|
20
|
+
exports.getCommentFor = getCommentFor;
|
|
21
|
+
exports.setCommentFor = (0, misc_1.singleFromBatch)(async (jobs) => {
|
|
22
|
+
const byFolder = lodash_1.default.groupBy(jobs, job => (0, path_1.dirname)(job[0]));
|
|
23
|
+
return Promise.allSettled(lodash_1.default.map(byFolder, async (jobs, folder) => {
|
|
24
|
+
const comments = await readDescription(folder).catch(() => new Map());
|
|
25
|
+
for (const [path, comment] of jobs) {
|
|
26
|
+
const file = path.slice(folder.length + 1);
|
|
27
|
+
if (!comment)
|
|
28
|
+
comments.delete(file);
|
|
29
|
+
else
|
|
30
|
+
comments.set(file, comment);
|
|
31
|
+
}
|
|
32
|
+
// encode comments in descript.ion format
|
|
33
|
+
let txt = '';
|
|
34
|
+
comments.forEach((c, f) => txt += (f.includes(' ') ? `"${f}"` : f) + ' ' + (c.includes('\n') ? c.replaceAll('\n', '\\n') + MULTILINE_SUFFIX : c) + '\n');
|
|
35
|
+
await (0, promises_1.writeFile)((0, path_1.join)(folder, exports.DESCRIPT_ION), txt);
|
|
36
|
+
}));
|
|
37
|
+
});
|
|
38
|
+
const MULTILINE_SUFFIX = '\x04\xc2';
|
|
39
|
+
function readDescription(path) {
|
|
40
|
+
return (0, util_files_1.parseFile)((0, path_1.join)(path, exports.DESCRIPT_ION), txt => new Map(txt.split('\n').map(line => {
|
|
41
|
+
const quoted = line[0] === '"' ? 1 : 0;
|
|
42
|
+
const i = quoted ? line.indexOf('"', 2) + 1 : line.indexOf(' ');
|
|
43
|
+
const fn = line.slice(quoted, i - quoted);
|
|
44
|
+
let comment = line.slice(i + 1);
|
|
45
|
+
if (comment.endsWith(MULTILINE_SUFFIX))
|
|
46
|
+
comment = comment.slice(0, -2).replaceAll('\\n', '\n');
|
|
47
|
+
return [fn, comment];
|
|
48
|
+
})));
|
|
49
|
+
}
|
package/src/config.js
CHANGED
|
@@ -89,11 +89,7 @@ function defineConfig(k, defaultValue, compiler) {
|
|
|
89
89
|
else
|
|
90
90
|
setConfig1(k, v);
|
|
91
91
|
},
|
|
92
|
-
compiled: () =>
|
|
93
|
-
if (!compiler)
|
|
94
|
-
throw "missing compiler";
|
|
95
|
-
return compiled;
|
|
96
|
-
}
|
|
92
|
+
compiled: () => compiled !== null && compiled !== void 0 ? compiled : (0, misc_1.throw_)("missing compiler"),
|
|
97
93
|
};
|
|
98
94
|
if (compiler)
|
|
99
95
|
ret.sub((...args) => compiled = compiler(...args));
|
package/src/const.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.APP_PATH = exports.IS_BINARY = exports.IS_MAC = exports.IS_WINDOWS = exports.HTTP_SERVICE_UNAVAILABLE = exports.HTTP_SERVER_ERROR = exports.HTTP_FAILED_DEPENDENCY = exports.HTTP_FOOL = exports.HTTP_RANGE_NOT_SATISFIABLE = exports.HTTP_PAYLOAD_TOO_LARGE = exports.HTTP_CONFLICT = exports.HTTP_NOT_ACCEPTABLE = exports.HTTP_METHOD_NOT_ALLOWED = exports.HTTP_NOT_FOUND = exports.HTTP_FORBIDDEN = exports.HTTP_UNAUTHORIZED = exports.HTTP_BAD_REQUEST = exports.HTTP_NOT_MODIFIED = exports.HTTP_TEMPORARY_REDIRECT = exports.HTTP_PARTIAL_CONTENT = exports.HTTP_NO_CONTENT = exports.HTTP_OK = exports.PLUGINS_PUB_URI = exports.API_URI = exports.ADMIN_URI = exports.FRONTEND_URI = exports.SPECIAL_URI = exports.HFS_REPO_BRANCH = exports.HFS_REPO = exports.COMPATIBLE_API_VERSION = exports.API_VERSION = exports.RUNNING_BETA = exports.VERSION = exports.BUILD_TIMESTAMP = exports.HFS_STARTED = exports.ORIGINAL_CWD = exports.DEV = exports.argv = void 0;
|
|
30
|
+
exports.APP_PATH = exports.IS_BINARY = exports.IS_MAC = exports.IS_WINDOWS = exports.HTTP_SERVICE_UNAVAILABLE = exports.HTTP_SERVER_ERROR = exports.HTTP_FAILED_DEPENDENCY = exports.HTTP_FOOL = exports.HTTP_RANGE_NOT_SATISFIABLE = exports.HTTP_PAYLOAD_TOO_LARGE = exports.HTTP_CONFLICT = exports.HTTP_NOT_ACCEPTABLE = exports.HTTP_METHOD_NOT_ALLOWED = exports.HTTP_NOT_FOUND = exports.HTTP_FORBIDDEN = exports.HTTP_UNAUTHORIZED = exports.HTTP_BAD_REQUEST = exports.HTTP_NOT_MODIFIED = exports.HTTP_TEMPORARY_REDIRECT = exports.HTTP_MOVED_PERMANENTLY = exports.HTTP_PARTIAL_CONTENT = exports.HTTP_NO_CONTENT = exports.HTTP_OK = exports.PLUGINS_PUB_URI = exports.API_URI = exports.ADMIN_URI = exports.FRONTEND_URI = exports.SPECIAL_URI = exports.HFS_REPO_BRANCH = exports.HFS_REPO = exports.COMPATIBLE_API_VERSION = exports.API_VERSION = exports.RUNNING_BETA = exports.VERSION = exports.BUILD_TIMESTAMP = exports.HFS_STARTED = exports.ORIGINAL_CWD = exports.DEV = exports.argv = void 0;
|
|
31
31
|
const minimist_1 = __importDefault(require("minimist"));
|
|
32
32
|
const fs = __importStar(require("fs"));
|
|
33
33
|
const os_1 = require("os");
|
|
@@ -38,7 +38,7 @@ exports.DEV = process.env.DEV || exports.argv.dev ? 'DEV' : '';
|
|
|
38
38
|
exports.ORIGINAL_CWD = process.cwd();
|
|
39
39
|
exports.HFS_STARTED = new Date();
|
|
40
40
|
const PKG_PATH = (0, path_1.join)(__dirname, '..', 'package.json');
|
|
41
|
-
exports.BUILD_TIMESTAMP = "2023-
|
|
41
|
+
exports.BUILD_TIMESTAMP = "2023-10-04T19:41:32.601Z";
|
|
42
42
|
const pkg = JSON.parse(fs.readFileSync(PKG_PATH, 'utf8'));
|
|
43
43
|
exports.VERSION = pkg.version;
|
|
44
44
|
exports.RUNNING_BETA = exports.VERSION.includes('-');
|
|
@@ -54,6 +54,7 @@ exports.PLUGINS_PUB_URI = exports.SPECIAL_URI + 'plugins/';
|
|
|
54
54
|
exports.HTTP_OK = 200;
|
|
55
55
|
exports.HTTP_NO_CONTENT = 204;
|
|
56
56
|
exports.HTTP_PARTIAL_CONTENT = 206;
|
|
57
|
+
exports.HTTP_MOVED_PERMANENTLY = 301;
|
|
57
58
|
exports.HTTP_TEMPORARY_REDIRECT = 302;
|
|
58
59
|
exports.HTTP_NOT_MODIFIED = 304;
|
|
59
60
|
exports.HTTP_BAD_REQUEST = 400;
|
package/src/cross.js
CHANGED
|
@@ -3,7 +3,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.
|
|
6
|
+
exports.isWindowsDrive = exports.isIP = exports.isPrimitive = exports.formatTimestamp = exports.repeat = exports.asyncGeneratorToArray = exports.filterMapGenerator = exports.throw_ = exports.hasProp = exports.typedEntries = exports.typedKeys = exports.objRenameKey = exports.randomId = exports.getOrSet = exports.waitFor = exports.newObj = exports.removeStarting = exports.findDefined = exports.isOrderedEqual = exports.swap = exports.tryJson = exports.basename = exports.pendingPromise = exports._dbg = exports._log = exports.wantArray = exports.formatPerc = exports.with_ = exports.try_ = exports.setHidden = exports.onlyTruthy = exports.truthy = exports.enforceFinal = exports.objSameKeys = exports.haveTimeout = exports.wait = exports.prefix = exports.formatBytes = exports.isWhoObject = exports.PERM_KEYS = exports.defaultPerms = exports.WHO_ANY_ACCOUNT = exports.WHO_NO_ONE = exports.WHO_ANYONE = exports.MAX_TILES_SIZE = exports.DAY = exports.HOUR = exports.MINUTE = exports.WIKI_URL = exports.REPO_URL = void 0;
|
|
7
|
+
exports.promiseBestEffort = exports.escapeHTML = exports.ipForUrl = exports.ipLocalHost = exports.xlate = exports.isEqualLax = void 0;
|
|
7
8
|
// This file is part of HFS - Copyright 2021-2023, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt
|
|
8
9
|
// all content here is shared between client and server
|
|
9
10
|
const lodash_1 = __importDefault(require("lodash"));
|
|
@@ -13,6 +14,22 @@ exports.MINUTE = 60000;
|
|
|
13
14
|
exports.HOUR = 60 * exports.MINUTE;
|
|
14
15
|
exports.DAY = 24 * exports.HOUR;
|
|
15
16
|
exports.MAX_TILES_SIZE = 10;
|
|
17
|
+
exports.WHO_ANYONE = true;
|
|
18
|
+
exports.WHO_NO_ONE = false;
|
|
19
|
+
exports.WHO_ANY_ACCOUNT = '*';
|
|
20
|
+
exports.defaultPerms = {
|
|
21
|
+
can_see: 'can_read',
|
|
22
|
+
can_read: exports.WHO_ANYONE,
|
|
23
|
+
can_list: 'can_read',
|
|
24
|
+
can_upload: exports.WHO_NO_ONE,
|
|
25
|
+
can_delete: exports.WHO_NO_ONE,
|
|
26
|
+
can_archive: 'can_read'
|
|
27
|
+
};
|
|
28
|
+
exports.PERM_KEYS = typedKeys(exports.defaultPerms);
|
|
29
|
+
function isWhoObject(v) {
|
|
30
|
+
return v !== null && typeof v === 'object' && !Array.isArray(v);
|
|
31
|
+
}
|
|
32
|
+
exports.isWhoObject = isWhoObject;
|
|
16
33
|
const MULTIPLIERS = ['', 'K', 'M', 'G', 'T'];
|
|
17
34
|
function formatBytes(n, { post = 'B', k = 1024, digits = NaN } = {}) {
|
|
18
35
|
if (isNaN(Number(n)) || n < 0)
|
|
@@ -86,6 +103,11 @@ function _log(...args) {
|
|
|
86
103
|
return args[args.length - 1];
|
|
87
104
|
}
|
|
88
105
|
exports._log = _log;
|
|
106
|
+
function _dbg(x) {
|
|
107
|
+
debugger;
|
|
108
|
+
return x;
|
|
109
|
+
}
|
|
110
|
+
exports._dbg = _dbg;
|
|
89
111
|
function pendingPromise() {
|
|
90
112
|
let takeOut;
|
|
91
113
|
const ret = new Promise((resolve, reject) => takeOut = { resolve, reject });
|
|
@@ -158,6 +180,7 @@ function newObj(src, returnNewValue, recur = false) {
|
|
|
158
180
|
return Object.fromEntries(onlyTruthy(pairs));
|
|
159
181
|
}
|
|
160
182
|
exports.newObj = newObj;
|
|
183
|
+
// returns undefined if timeout is reached
|
|
161
184
|
async function waitFor(cb, { interval = 200, timeout = Infinity } = {}) {
|
|
162
185
|
const started = Date.now();
|
|
163
186
|
while (1) {
|
|
@@ -225,7 +248,7 @@ async function asyncGeneratorToArray(generator) {
|
|
|
225
248
|
}
|
|
226
249
|
exports.asyncGeneratorToArray = asyncGeneratorToArray;
|
|
227
250
|
function repeat(every, cb) {
|
|
228
|
-
Promise.allSettled([cb()]).then(() => setTimeout(() => repeat(every, cb), every));
|
|
251
|
+
return Promise.allSettled([cb()]).then(() => setTimeout(() => repeat(every, cb), every));
|
|
229
252
|
}
|
|
230
253
|
exports.repeat = repeat;
|
|
231
254
|
function formatTimestamp(x) {
|
|
@@ -255,3 +278,21 @@ function xlate(input, table) {
|
|
|
255
278
|
return (_a = table[input]) !== null && _a !== void 0 ? _a : input;
|
|
256
279
|
}
|
|
257
280
|
exports.xlate = xlate;
|
|
281
|
+
function ipLocalHost(ip) {
|
|
282
|
+
return ip === '::1' || ip.endsWith('127.0.0.1');
|
|
283
|
+
}
|
|
284
|
+
exports.ipLocalHost = ipLocalHost;
|
|
285
|
+
function ipForUrl(ip) {
|
|
286
|
+
return ip.includes(':') ? '[' + ip + ']' : ip;
|
|
287
|
+
}
|
|
288
|
+
exports.ipForUrl = ipForUrl;
|
|
289
|
+
function escapeHTML(text) {
|
|
290
|
+
return text.replace(/[\u0000-\u002F\u003A-\u0040\u005B-\u0060\u007B-\u00FF]/g, c => '&#' + ('000' + c.charCodeAt(0)).slice(-4) + ';');
|
|
291
|
+
}
|
|
292
|
+
exports.escapeHTML = escapeHTML;
|
|
293
|
+
// wait for all, but returns only those that resolved
|
|
294
|
+
async function promiseBestEffort(promises) {
|
|
295
|
+
const res = await Promise.allSettled(promises);
|
|
296
|
+
return res.filter(x => x.status === 'fulfilled').map((x) => x.value);
|
|
297
|
+
}
|
|
298
|
+
exports.promiseBestEffort = promiseBestEffort;
|
package/src/customHtml.js
CHANGED
|
@@ -35,7 +35,7 @@ function watchLoadCustomHtml(folder = '') {
|
|
|
35
35
|
exports.watchLoadCustomHtml = watchLoadCustomHtml;
|
|
36
36
|
function getSection(name) {
|
|
37
37
|
return (exports.customHtmlState.sections.get(name) || '')
|
|
38
|
-
+ (0, plugins_1.mapPlugins)(pl => pl.getData().customHtml.get(name)).join('\n');
|
|
38
|
+
+ (0, plugins_1.mapPlugins)(pl => { var _a; return (_a = pl.getData().customHtml) === null || _a === void 0 ? void 0 : _a.get(name); }).join('\n');
|
|
39
39
|
}
|
|
40
40
|
exports.getSection = getSection;
|
|
41
41
|
async function saveCustomHtml(sections) {
|
package/src/debounceAsync.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
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
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.singleFromBatch = exports.debounceAsync = void 0;
|
|
4
5
|
// like lodash.debounce, but also avoids async invocations to overlap
|
|
5
6
|
function debounceAsync(callback, wait = 100, options = {}) {
|
|
6
7
|
const { leading = false, maxWait = Infinity, cancelable = false, retain = 0, retainFailure } = options;
|
|
@@ -14,7 +15,9 @@ function debounceAsync(callback, wait = 100, options = {}) {
|
|
|
14
15
|
let lastSince = 0;
|
|
15
16
|
const interceptingWrapper = (...args) => runningDebouncer = debouncer(...args);
|
|
16
17
|
return Object.assign(interceptingWrapper, {
|
|
18
|
+
clearRetain: () => last = undefined,
|
|
17
19
|
flush: () => runningCallback !== null && runningCallback !== void 0 ? runningCallback : exec(),
|
|
20
|
+
isWorking: () => runningCallback,
|
|
18
21
|
...cancelable && {
|
|
19
22
|
cancel() {
|
|
20
23
|
waitingSince = 0;
|
|
@@ -58,4 +61,18 @@ function debounceAsync(callback, wait = 100, options = {}) {
|
|
|
58
61
|
}
|
|
59
62
|
}
|
|
60
63
|
}
|
|
61
|
-
exports.
|
|
64
|
+
exports.debounceAsync = debounceAsync;
|
|
65
|
+
// given a function that works on a batch of requests, returns the function that works on a single request
|
|
66
|
+
function singleFromBatch(batchWorker) {
|
|
67
|
+
let batch = [];
|
|
68
|
+
const debounced = debounceAsync(async () => {
|
|
69
|
+
const ret = batchWorker(batch);
|
|
70
|
+
batch = [];
|
|
71
|
+
return ret;
|
|
72
|
+
});
|
|
73
|
+
return (...args) => {
|
|
74
|
+
batch.push(args);
|
|
75
|
+
return debounced();
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
exports.singleFromBatch = singleFromBatch;
|
package/src/frontEndApis.js
CHANGED
|
@@ -39,6 +39,7 @@ const promises_1 = require("fs/promises");
|
|
|
39
39
|
const path_1 = require("path");
|
|
40
40
|
const upload_1 = require("./upload");
|
|
41
41
|
const misc_1 = require("./misc");
|
|
42
|
+
const comments_1 = require("./comments");
|
|
42
43
|
exports.frontEndApis = {
|
|
43
44
|
get_file_list: api_file_list_1.get_file_list,
|
|
44
45
|
...api_auth,
|
|
@@ -95,6 +96,7 @@ exports.frontEndApis = {
|
|
|
95
96
|
throw new apiMiddleware_1.ApiError(const_1.HTTP_UNAUTHORIZED);
|
|
96
97
|
try {
|
|
97
98
|
await (0, promises_1.rm)(node.source, { recursive: true });
|
|
99
|
+
(0, comments_1.setCommentFor)(node.source, '').then();
|
|
98
100
|
return {};
|
|
99
101
|
}
|
|
100
102
|
catch (e) {
|
|
@@ -111,18 +113,38 @@ exports.frontEndApis = {
|
|
|
111
113
|
if (!(0, vfs_1.hasPermission)(node, 'can_delete', ctx))
|
|
112
114
|
throw new apiMiddleware_1.ApiError(const_1.HTTP_UNAUTHORIZED);
|
|
113
115
|
try {
|
|
114
|
-
if (node.name)
|
|
116
|
+
if (node.name) // virtual name = virtual rename
|
|
115
117
|
node.name = dest;
|
|
116
|
-
else
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
118
|
+
else {
|
|
119
|
+
if (!node.source)
|
|
120
|
+
throw new apiMiddleware_1.ApiError(const_1.HTTP_FAILED_DEPENDENCY);
|
|
121
|
+
const destSource = (0, path_1.join)((0, path_1.dirname)(node.source), dest);
|
|
122
|
+
await (0, promises_1.rename)(node.source, destSource);
|
|
123
|
+
(0, comments_1.getCommentFor)(node.source).then(c => {
|
|
124
|
+
if (!c)
|
|
125
|
+
return;
|
|
126
|
+
(0, comments_1.setCommentFor)(node.source, '').then();
|
|
127
|
+
(0, comments_1.setCommentFor)(destSource, c).then();
|
|
128
|
+
});
|
|
129
|
+
}
|
|
120
130
|
return {};
|
|
121
131
|
}
|
|
122
132
|
catch (e) {
|
|
123
133
|
throw new apiMiddleware_1.ApiError(const_1.HTTP_SERVER_ERROR, e);
|
|
124
134
|
}
|
|
125
|
-
}
|
|
135
|
+
},
|
|
136
|
+
async comment({ uri, comment }, ctx) {
|
|
137
|
+
(0, misc_1.apiAssertTypes)({ string: { uri, comment } });
|
|
138
|
+
const node = await (0, vfs_1.urlToNode)(uri, ctx);
|
|
139
|
+
if (!node)
|
|
140
|
+
throw new apiMiddleware_1.ApiError(const_1.HTTP_NOT_FOUND);
|
|
141
|
+
if (!(0, vfs_1.hasPermission)(node, 'can_upload', ctx))
|
|
142
|
+
throw new apiMiddleware_1.ApiError(const_1.HTTP_UNAUTHORIZED);
|
|
143
|
+
if (!node.source)
|
|
144
|
+
throw new apiMiddleware_1.ApiError(const_1.HTTP_FAILED_DEPENDENCY);
|
|
145
|
+
await (0, comments_1.setCommentFor)(node.source, comment);
|
|
146
|
+
return {};
|
|
147
|
+
},
|
|
126
148
|
};
|
|
127
149
|
function notifyClient(ctx, name, data) {
|
|
128
150
|
const { notificationChannel } = ctx.query;
|
package/src/github.js
CHANGED
|
@@ -26,35 +26,37 @@ function downloadProgress(id, status) {
|
|
|
26
26
|
// determine default branch, possibly without consuming api quota
|
|
27
27
|
async function getGithubDefaultBranch(repo) {
|
|
28
28
|
var _a;
|
|
29
|
-
const
|
|
30
|
-
return
|
|
31
|
-
: (_a = (await getRepoInfo(repo))) === null || _a === void 0 ? void 0 : _a.default_branch;
|
|
29
|
+
const test = await (0, misc_1.httpString)(`https://github.com/${repo}/archive/refs/heads/main.zip`, { method: 'HEAD' }).then(() => 1, () => 0);
|
|
30
|
+
return test ? 'main' : (_a = (await getRepoInfo(repo))) === null || _a === void 0 ? void 0 : _a.default_branch;
|
|
32
31
|
}
|
|
33
32
|
async function downloadPlugin(repo, { branch = '', overwrite = false } = {}) {
|
|
34
|
-
var _a, _b, _c;
|
|
33
|
+
var _a, _b, _c, _d;
|
|
35
34
|
if (typeof repo !== 'string')
|
|
36
35
|
repo = repo.main;
|
|
37
36
|
if (downloading[repo])
|
|
38
|
-
|
|
37
|
+
throw new apiMiddleware_1.ApiError(const_1.HTTP_CONFLICT, "already downloading");
|
|
38
|
+
const projectInfo = await (0, exports.getProjectInfo)();
|
|
39
|
+
if ((_a = projectInfo === null || projectInfo === void 0 ? void 0 : projectInfo.plugins_blacklist) === null || _a === void 0 ? void 0 : _a.includes(repo))
|
|
40
|
+
throw new apiMiddleware_1.ApiError(const_1.HTTP_FORBIDDEN, "blacklisted");
|
|
39
41
|
console.log('downloading plugin', repo);
|
|
40
42
|
downloadProgress(repo, true);
|
|
41
43
|
try {
|
|
42
44
|
if (repo.includes('//')) { // custom repo
|
|
43
45
|
const pl = (0, plugins_1.findPluginByRepo)(repo);
|
|
44
46
|
if (!pl)
|
|
45
|
-
|
|
46
|
-
const customRepo = (((
|
|
47
|
+
throw new apiMiddleware_1.ApiError(const_1.HTTP_BAD_REQUEST, "bad repo");
|
|
48
|
+
const customRepo = (((_c = (_b = pl).getData) === null || _c === void 0 ? void 0 : _c.call(_b)) || pl).repo;
|
|
47
49
|
let url = customRepo === null || customRepo === void 0 ? void 0 : customRepo.zip;
|
|
48
50
|
if (!url)
|
|
49
|
-
|
|
51
|
+
throw new apiMiddleware_1.ApiError(const_1.HTTP_SERVER_ERROR, "bad plugin");
|
|
50
52
|
if (!url.includes('//'))
|
|
51
53
|
url = customRepo.web + url;
|
|
52
|
-
return await go(url, pl === null || pl === void 0 ? void 0 : pl.id, (
|
|
54
|
+
return await go(url, pl === null || pl === void 0 ? void 0 : pl.id, (_d = customRepo.zipRoot) !== null && _d !== void 0 ? _d : DIST_ROOT);
|
|
53
55
|
}
|
|
54
56
|
branch || (branch = await getGithubDefaultBranch(repo));
|
|
55
57
|
const short = repo.split('/')[1]; // second part, repo without the owner
|
|
56
58
|
if (!short)
|
|
57
|
-
|
|
59
|
+
throw new apiMiddleware_1.ApiError(const_1.HTTP_BAD_REQUEST, "bad repo");
|
|
58
60
|
const folder = overwrite ? lodash_1.default.findKey(getFolder2repo(), x => x === repo) // use existing folder
|
|
59
61
|
: getFolder2repo().hasOwnProperty(short) ? repo.replace('/', '-') // longer form only if another plugin is using short form, to avoid overwriting
|
|
60
62
|
: short;
|
|
@@ -96,8 +98,7 @@ function getRepoInfo(id) {
|
|
|
96
98
|
}
|
|
97
99
|
exports.getRepoInfo = getRepoInfo;
|
|
98
100
|
function readGithubFile(uri) {
|
|
99
|
-
return (0, misc_1.httpString)('https://raw.githubusercontent.com/' + uri)
|
|
100
|
-
.then(res => res.body);
|
|
101
|
+
return (0, misc_1.httpString)('https://raw.githubusercontent.com/' + uri);
|
|
101
102
|
}
|
|
102
103
|
exports.readGithubFile = readGithubFile;
|
|
103
104
|
async function readOnlinePlugin(repo, branch = '') {
|
|
@@ -111,10 +112,7 @@ async function readOnlinePlugin(repo, branch = '') {
|
|
|
111
112
|
throw Error("missing repo.main");
|
|
112
113
|
if (!main.includes('//'))
|
|
113
114
|
main = pl.repo.web + main;
|
|
114
|
-
|
|
115
|
-
if (!res.ok)
|
|
116
|
-
throw Error("bad repo.main");
|
|
117
|
-
return (0, plugins_1.parsePluginSource)(main, res.body); // use 'repo' as 'id' client-side
|
|
115
|
+
return (0, plugins_1.parsePluginSource)(main, await (0, misc_1.httpString)(main)); // use 'repo' as 'id' client-side
|
|
118
116
|
}
|
|
119
117
|
branch || (branch = await getGithubDefaultBranch(repo));
|
|
120
118
|
const res = await readGithubFile(`${repo}/${branch}/${DIST_ROOT}/plugin.js`);
|
|
@@ -132,22 +130,16 @@ function getFolder2repo() {
|
|
|
132
130
|
}
|
|
133
131
|
exports.getFolder2repo = getFolder2repo;
|
|
134
132
|
async function apiGithub(uri) {
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
});
|
|
142
|
-
if (!res.ok)
|
|
143
|
-
throw res.statusCode;
|
|
144
|
-
return JSON.parse(res.body);
|
|
145
|
-
}
|
|
146
|
-
catch (e) {
|
|
133
|
+
return (0, misc_1.httpString)('https://api.github.com/' + uri, {
|
|
134
|
+
headers: {
|
|
135
|
+
'User-Agent': 'HFS',
|
|
136
|
+
Accept: 'application/vnd.github.v3+json',
|
|
137
|
+
}
|
|
138
|
+
}).then(JSON.parse, e => {
|
|
147
139
|
// https://docs.github.com/en/rest/overview/resources-in-the-rest-api?apiVersion=2022-11-28#rate-limiting
|
|
148
140
|
throw e.message === '403' ? Error('github_quota')
|
|
149
141
|
: e;
|
|
150
|
-
}
|
|
142
|
+
});
|
|
151
143
|
}
|
|
152
144
|
async function searchPlugins(text = '') {
|
|
153
145
|
const projectInfo = await (0, exports.getProjectInfo)();
|
|
@@ -188,5 +180,7 @@ exports.searchPlugins = searchPlugins;
|
|
|
188
180
|
// centralized hosted information, to be used as little as possible
|
|
189
181
|
const FN = 'central.json';
|
|
190
182
|
let builtIn = JSON.parse((0, fs_1.readFileSync)((0, path_1.join)(__dirname, '..', FN), 'utf8'));
|
|
191
|
-
exports.getProjectInfo = (0, misc_1.debounceAsync)(() => readGithubFile(`${const_1.HFS_REPO}/${const_1.HFS_REPO_BRANCH}/${FN}`)
|
|
183
|
+
exports.getProjectInfo = (0, misc_1.debounceAsync)(() => readGithubFile(`${const_1.HFS_REPO}/${const_1.HFS_REPO_BRANCH}/${FN}`)
|
|
184
|
+
.then(JSON.parse, () => null)
|
|
185
|
+
.then(x => Object.assign(Object.create(builtIn), const_1.DEV ? null : x)), // fall back to built-in
|
|
192
186
|
0, { retain: misc_1.DAY, retainFailure: 60000 });
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"author": "Massimo Melina",
|
|
3
3
|
"version": 1.8,
|
|
4
|
-
"hfs_version": "0.
|
|
4
|
+
"hfs_version": "0.49.0",
|
|
5
5
|
"translate": {
|
|
6
6
|
"Select": "Seleziona",
|
|
7
7
|
"n_files": "{n} file",
|
|
@@ -121,8 +121,10 @@
|
|
|
121
121
|
"showHelp_F_body": "pieno schermo",
|
|
122
122
|
"Destination": "Destinazione",
|
|
123
123
|
"in_queue": "{n} in coda",
|
|
124
|
-
"": "
|
|
125
|
-
"": "
|
|
124
|
+
"enter_comment": "Comment for this file",
|
|
125
|
+
"Comment": "Comment",
|
|
126
|
+
"_PLUGINS SECTION": "",
|
|
127
|
+
"_PLUGIN thumbnails": "",
|
|
126
128
|
"Enable tiles mode": "Modalità griglia",
|
|
127
129
|
"thumbnails_switchBack": "Puoi tornare alla lista com'era prima entrando in Opzioni"
|
|
128
130
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"author": "Mefistofell, SanokKule",
|
|
3
|
-
"version": 2.
|
|
4
|
-
"hfs_version": "0.
|
|
3
|
+
"version": 2.2,
|
|
4
|
+
"hfs_version": "0.49.0",
|
|
5
5
|
"translate": {
|
|
6
6
|
"Select": "Выбор",
|
|
7
7
|
"n_files": "{n,plural, one{1 файл} few{# файла} other{# файлов}}",
|
|
@@ -129,8 +129,10 @@
|
|
|
129
129
|
"showHelp_F_body": "полноэкранный режим",
|
|
130
130
|
"Destination": "Назначение",
|
|
131
131
|
"in_queue": "{n} в очереди",
|
|
132
|
-
"": "
|
|
133
|
-
"": "
|
|
132
|
+
"enter_comment": "Комментарий для файла",
|
|
133
|
+
"Comment": "Комментировать",
|
|
134
|
+
"_PLUGINS SECTION": "",
|
|
135
|
+
"_PLUGIN thumbnails": "",
|
|
134
136
|
"Enable tiles mode": "Просмотр в режиме плиток",
|
|
135
137
|
"thumbnails_switchBack": "Изменить вид можно в настройках"
|
|
136
138
|
}
|
package/src/listen.js
CHANGED
|
@@ -43,6 +43,8 @@ const adminApis_1 = require("./adminApis");
|
|
|
43
43
|
const lodash_1 = __importDefault(require("lodash"));
|
|
44
44
|
const crypto_1 = require("crypto");
|
|
45
45
|
const api_net_1 = require("./api.net");
|
|
46
|
+
const events_1 = __importDefault(require("./events"));
|
|
47
|
+
const net_1 = require("net");
|
|
46
48
|
let httpSrv;
|
|
47
49
|
let httpsSrv;
|
|
48
50
|
const openBrowserAtStart = (0, config_1.defineConfig)('open_browser_at_start', !const_1.DEV);
|
|
@@ -52,13 +54,11 @@ function getHttpsWorkingPort() {
|
|
|
52
54
|
}
|
|
53
55
|
exports.getHttpsWorkingPort = getHttpsWorkingPort;
|
|
54
56
|
const commonOptions = { requestTimeout: 0 };
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
while (!index_1.app)
|
|
58
|
-
await (0, misc_1.wait)(100);
|
|
57
|
+
const considerHttp = (0, misc_1.debounceAsync)(async () => {
|
|
58
|
+
await (0, misc_1.waitFor)(() => index_1.app);
|
|
59
59
|
stopServer(httpSrv).then();
|
|
60
60
|
httpSrv = Object.assign(http.createServer(commonOptions, index_1.app.callback()), { name: 'http' });
|
|
61
|
-
port = await startServer(httpSrv, { port });
|
|
61
|
+
const port = await startServer(httpSrv, { port: exports.portCfg.get(), host: listenInterface.get() });
|
|
62
62
|
if (!port)
|
|
63
63
|
return;
|
|
64
64
|
httpSrv.on('connection', connections_1.newConnection);
|
|
@@ -66,6 +66,10 @@ exports.portCfg.sub(async (port) => {
|
|
|
66
66
|
if (openBrowserAtStart.get() && !const_1.argv.updated)
|
|
67
67
|
openAdmin();
|
|
68
68
|
});
|
|
69
|
+
exports.portCfg = (0, config_1.defineConfig)('port', 80);
|
|
70
|
+
const listenInterface = (0, config_1.defineConfig)('listen_interface', '');
|
|
71
|
+
exports.portCfg.sub(considerHttp);
|
|
72
|
+
listenInterface.sub(considerHttp);
|
|
69
73
|
function openAdmin() {
|
|
70
74
|
for (const srv of [httpSrv, httpsSrv]) {
|
|
71
75
|
const a = srv === null || srv === void 0 ? void 0 : srv.address();
|
|
@@ -84,6 +88,8 @@ function openAdmin() {
|
|
|
84
88
|
}
|
|
85
89
|
exports.openAdmin = openAdmin;
|
|
86
90
|
function getCertObject() {
|
|
91
|
+
if (!httpsOptions.cert)
|
|
92
|
+
return;
|
|
87
93
|
const o = new crypto_1.X509Certificate(httpsOptions.cert);
|
|
88
94
|
const some = lodash_1.default.pick(o, ['subject', 'issuer', 'validFrom', 'validTo']);
|
|
89
95
|
return (0, misc_1.objSameKeys)(some, v => (v === null || v === void 0 ? void 0 : v.includes('=')) ? Object.fromEntries(v.split('\n').map(x => x.split('='))) : v);
|
|
@@ -99,6 +105,8 @@ const considerHttps = (0, misc_1.debounceAsync)(async () => {
|
|
|
99
105
|
httpsSrv = Object.assign(https.createServer(port === PORT_DISABLED ? {} : { ...commonOptions, key: httpsOptions.private_key, cert: httpsOptions.cert }, index_1.app.callback()), { name: 'https' });
|
|
100
106
|
if (port >= 0) {
|
|
101
107
|
const cert = getCertObject();
|
|
108
|
+
if (!cert)
|
|
109
|
+
return;
|
|
102
110
|
const cn = (_a = cert.subject) === null || _a === void 0 ? void 0 : _a.CN;
|
|
103
111
|
if (cn)
|
|
104
112
|
console.log("certificate loaded for", cn);
|
|
@@ -120,11 +128,12 @@ const considerHttps = (0, misc_1.debounceAsync)(async () => {
|
|
|
120
128
|
console.log("failed to create https server: check your private key and certificate", String(e));
|
|
121
129
|
return;
|
|
122
130
|
}
|
|
123
|
-
port = await startServer(httpsSrv, { port });
|
|
131
|
+
port = await startServer(httpsSrv, { port, host: listenInterface.get() });
|
|
124
132
|
if (!port)
|
|
125
133
|
return;
|
|
126
134
|
httpsSrv.on('connection', connections_1.newConnection);
|
|
127
135
|
printUrls(httpsSrv.name);
|
|
136
|
+
events_1.default.emit('https ready');
|
|
128
137
|
});
|
|
129
138
|
exports.cert = (0, config_1.defineConfig)('cert', '');
|
|
130
139
|
exports.privateKey = (0, config_1.defineConfig)('private_key', '');
|
|
@@ -150,6 +159,7 @@ for (const cfg of httpsNeeds) {
|
|
|
150
159
|
const PORT_DISABLED = -1;
|
|
151
160
|
exports.httpsPortCfg = (0, config_1.defineConfig)('https_port', PORT_DISABLED);
|
|
152
161
|
exports.httpsPortCfg.sub(considerHttps);
|
|
162
|
+
listenInterface.sub(considerHttps);
|
|
153
163
|
function startServer(srv, { port, host }) {
|
|
154
164
|
return new Promise(async (resolve) => {
|
|
155
165
|
if (!srv)
|
|
@@ -194,6 +204,8 @@ function startServer(srv, { port, host }) {
|
|
|
194
204
|
srv.error = String(e);
|
|
195
205
|
srv.busy = undefined;
|
|
196
206
|
const { code } = e;
|
|
207
|
+
if (code === 'EACCES' && port < 1024)
|
|
208
|
+
srv.error = `lacking permission on port ${port}, try with permission (${const_1.IS_WINDOWS ? 'administrator' : 'sudo'}) or port > 1024`;
|
|
197
209
|
if (code === 'EADDRINUSE') {
|
|
198
210
|
srv.busy = (0, find_process_1.default)('port', port).then(res => { var _a; return ((_a = res === null || res === void 0 ? void 0 : res[0]) === null || _a === void 0 ? void 0 : _a.name) || ''; }, () => '');
|
|
199
211
|
srv.error = `port ${port} busy: ${await srv.busy || "unknown process"}`;
|
|
@@ -242,36 +254,34 @@ async function getServerStatus() {
|
|
|
242
254
|
}
|
|
243
255
|
exports.getServerStatus = getServerStatus;
|
|
244
256
|
const ignore = /^(lo|.*loopback.*|virtualbox.*|.*\(wsl\).*|llw\d|awdl\d|utun\d|anpi\d)$/i; // avoid giving too much information
|
|
245
|
-
async function getIps() {
|
|
257
|
+
async function getIps(external = true) {
|
|
246
258
|
const ips = (0, misc_1.onlyTruthy)(Object.entries((0, os_1.networkInterfaces)()).map(([name, nets]) => nets && !ignore.test(name)
|
|
247
259
|
&& v4first((0, misc_1.onlyTruthy)(nets.map(net => !net.internal && net.address)))[0] // for each interface we consider only 1 address
|
|
248
260
|
)).flat();
|
|
249
|
-
const e =
|
|
261
|
+
const e = external && api_net_1.externalIp;
|
|
250
262
|
if (e && !ips.includes(e))
|
|
251
263
|
ips.unshift(e);
|
|
252
264
|
return v4first(ips)
|
|
253
265
|
.filter((x, i, a) => a.length > 1 || !x.startsWith('169.254')); // 169.254 = dhcp failure on the interface, but keep it if it's our only one
|
|
254
266
|
function v4first(a) {
|
|
255
|
-
return lodash_1.default.sortBy(a,
|
|
267
|
+
return lodash_1.default.sortBy(a, net_1.isIPv6); // works because `false` comes first
|
|
256
268
|
}
|
|
257
269
|
}
|
|
258
270
|
exports.getIps = getIps;
|
|
259
271
|
async function getUrls() {
|
|
260
|
-
const
|
|
272
|
+
const on = listenInterface.get();
|
|
273
|
+
const ips = on ? [on] : await getIps();
|
|
261
274
|
return Object.fromEntries((0, misc_1.onlyTruthy)([httpSrv, httpsSrv].map(srv => {
|
|
262
275
|
var _a;
|
|
263
276
|
if (!(srv === null || srv === void 0 ? void 0 : srv.listening))
|
|
264
277
|
return false;
|
|
265
278
|
const port = (_a = srv === null || srv === void 0 ? void 0 : srv.address()) === null || _a === void 0 ? void 0 : _a.port;
|
|
266
279
|
const appendPort = port === (srv.name === 'https' ? 443 : 80) ? '' : ':' + port;
|
|
267
|
-
const urls = ips.map(ip => `${srv.name}://${ip}${appendPort}`);
|
|
280
|
+
const urls = ips.map(ip => `${srv.name}://${(0, misc_1.ipForUrl)(ip)}${appendPort}`);
|
|
268
281
|
return urls.length && [srv.name, urls];
|
|
269
282
|
})));
|
|
270
283
|
}
|
|
271
284
|
exports.getUrls = getUrls;
|
|
272
285
|
function printUrls(srvName) {
|
|
273
|
-
getUrls().then(urls =>
|
|
274
|
-
for (const url of urls[srvName])
|
|
275
|
-
console.log('serving on', url);
|
|
276
|
-
});
|
|
286
|
+
getUrls().then(urls => lodash_1.default.each(urls[srvName], url => console.log('serving on', url)));
|
|
277
287
|
}
|
package/src/middlewares.js
CHANGED
|
@@ -224,7 +224,9 @@ const prepareState = async (ctx, next) => {
|
|
|
224
224
|
if (ctx.session)
|
|
225
225
|
ctx.session.maxAge = exports.sessionDuration.compiled();
|
|
226
226
|
// calculate these once and for all
|
|
227
|
-
ctx.state.account = (_a = await getHttpAccount(ctx)) !== null && _a !== void 0 ? _a : (0, perm_1.getAccount)((_b = ctx.session) === null || _b === void 0 ? void 0 : _b.username, false);
|
|
227
|
+
const a = ctx.state.account = (_a = await getHttpAccount(ctx)) !== null && _a !== void 0 ? _a : (0, perm_1.getAccount)((_b = ctx.session) === null || _b === void 0 ? void 0 : _b.username, false);
|
|
228
|
+
if (a && !(0, perm_1.accountCanLogin)(a))
|
|
229
|
+
ctx.state.account = undefined;
|
|
228
230
|
const conn = ctx.state.connection = (0, connections_1.socket2connection)(ctx.socket);
|
|
229
231
|
ctx.state.revProxyPath = ctx.get('x-forwarded-prefix');
|
|
230
232
|
if (conn)
|