hfs 0.26.8 → 0.27.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -2
- package/admin/assets/index-509bb1d6.js +415 -0
- package/admin/assets/index-60a380a7.css +1 -0
- package/admin/assets/sha512-738f0943.js +8 -0
- package/admin/index.html +3 -1
- package/admin/{public/logo.svg → logo.svg} +0 -0
- package/frontend/assets/index-6e178dfd.css +1 -0
- package/frontend/assets/index-aea7654e.js +85 -0
- package/frontend/assets/sha512-bf915587.js +8 -0
- package/frontend/{public/fontello.css → fontello.css} +0 -0
- package/frontend/{public/fontello.woff2 → fontello.woff2} +0 -0
- package/frontend/index.html +4 -2
- package/package.json +2 -6
- package/plugins/vhosting/plugin.js +23 -20
- package/src/QuickZipStream.js +285 -0
- package/src/ThrottledStream.js +93 -0
- package/src/adminApis.js +169 -0
- package/src/api.accounts.js +59 -0
- package/src/api.auth.js +128 -0
- package/src/api.file_list.js +110 -0
- package/src/api.helpers.js +32 -0
- package/src/api.monitor.js +104 -0
- package/src/api.plugins.js +128 -0
- package/src/api.vfs.js +167 -0
- package/src/apiMiddleware.js +123 -0
- package/src/block.js +34 -0
- package/src/commands.js +125 -0
- package/src/config.js +168 -0
- package/src/connections.js +57 -0
- package/src/const.js +94 -0
- package/src/crypt.js +21 -0
- package/src/debounceAsync.js +49 -0
- package/src/events.js +9 -0
- package/src/frontEndApis.js +38 -0
- package/src/github.js +104 -0
- package/src/index.js +57 -0
- package/src/listen.js +235 -0
- package/src/log.js +137 -0
- package/src/middlewares.js +195 -0
- package/src/misc.js +160 -0
- package/src/pbkdf2.js +74 -0
- package/src/perm.js +183 -0
- package/src/plugins.js +343 -0
- package/src/serveFile.js +105 -0
- package/src/serveGuiFiles.js +113 -0
- package/src/sse.js +30 -0
- package/src/throttler.js +91 -0
- package/src/update.js +70 -0
- package/src/util-files.js +163 -0
- package/src/util-generators.js +31 -0
- package/src/util-http.js +32 -0
- package/src/vfs.js +232 -0
- package/src/watchLoad.js +73 -0
- package/src/zip.js +73 -0
- package/admin/.DS_Store +0 -0
- package/admin/.eslintrc +0 -8
- package/admin/.gitignore +0 -23
- package/admin/package.json +0 -67
- package/admin/src/AccountForm.ts +0 -92
- package/admin/src/AccountsPage.ts +0 -143
- package/admin/src/App.ts +0 -83
- package/admin/src/ArrayField.ts +0 -84
- package/admin/src/ConfigPage.ts +0 -279
- package/admin/src/FileField.ts +0 -52
- package/admin/src/FileForm.ts +0 -148
- package/admin/src/FilePicker.ts +0 -166
- package/admin/src/HomePage.ts +0 -96
- package/admin/src/InstalledPlugins.ts +0 -158
- package/admin/src/LoginRequired.ts +0 -75
- package/admin/src/LogoutPage.ts +0 -27
- package/admin/src/LogsPage.ts +0 -75
- package/admin/src/MainMenu.ts +0 -74
- package/admin/src/MenuButton.ts +0 -38
- package/admin/src/MonitorPage.ts +0 -200
- package/admin/src/OnlinePlugins.ts +0 -101
- package/admin/src/PermField.ts +0 -80
- package/admin/src/PluginsPage.ts +0 -27
- package/admin/src/VfsMenuBar.ts +0 -58
- package/admin/src/VfsPage.ts +0 -124
- package/admin/src/VfsTree.ts +0 -95
- package/admin/src/addFiles.ts +0 -59
- package/admin/src/api.ts +0 -246
- package/admin/src/dialog.ts +0 -203
- package/admin/src/index.css +0 -21
- package/admin/src/index.ts +0 -10
- package/admin/src/md.ts +0 -31
- package/admin/src/misc.ts +0 -141
- package/admin/src/react-app-env.d.ts +0 -1
- package/admin/src/reportWebVitals.ts +0 -15
- package/admin/src/setupTests.ts +0 -5
- package/admin/src/state.ts +0 -40
- package/admin/src/theme.ts +0 -37
- package/admin/tsconfig.json +0 -26
- package/admin/vite.config.ts +0 -32
- package/frontend/.DS_Store +0 -0
- package/frontend/.eslintrc +0 -8
- package/frontend/.gitignore +0 -23
- package/frontend/package.json +0 -51
- package/frontend/src/App.ts +0 -25
- package/frontend/src/Breadcrumbs.ts +0 -43
- package/frontend/src/BrowseFiles.ts +0 -141
- package/frontend/src/Head.ts +0 -45
- package/frontend/src/UserPanel.ts +0 -52
- package/frontend/src/api.ts +0 -78
- package/frontend/src/components.ts +0 -54
- package/frontend/src/dialog.css +0 -76
- package/frontend/src/dialog.ts +0 -105
- package/frontend/src/icons.ts +0 -46
- package/frontend/src/index.scss +0 -307
- package/frontend/src/index.ts +0 -10
- package/frontend/src/login.ts +0 -50
- package/frontend/src/menu.ts +0 -188
- package/frontend/src/misc.ts +0 -54
- package/frontend/src/options.ts +0 -52
- package/frontend/src/react-app-env.d.ts +0 -1
- package/frontend/src/reportWebVitals.ts +0 -15
- package/frontend/src/setupTests.ts +0 -5
- package/frontend/src/state.ts +0 -82
- package/frontend/src/useAuthorized.ts +0 -17
- package/frontend/src/useFetchList.ts +0 -144
- package/frontend/src/useTheme.ts +0 -23
- package/frontend/tsconfig.json +0 -26
- package/frontend/vite.config.ts +0 -21
- package/src/QuickZipStream.ts +0 -279
- package/src/ThrottledStream.ts +0 -98
- package/src/adminApis.ts +0 -161
- package/src/api.accounts.ts +0 -78
- package/src/api.auth.ts +0 -131
- package/src/api.file_list.ts +0 -102
- package/src/api.helpers.ts +0 -30
- package/src/api.monitor.ts +0 -106
- package/src/api.plugins.ts +0 -139
- package/src/api.vfs.ts +0 -182
- package/src/apiMiddleware.ts +0 -124
- package/src/block.ts +0 -35
- package/src/commands.ts +0 -122
- package/src/config.ts +0 -166
- package/src/connections.ts +0 -60
- package/src/const.ts +0 -57
- package/src/crypt.ts +0 -16
- package/src/debounceAsync.ts +0 -51
- package/src/events.ts +0 -6
- package/src/frontEndApis.ts +0 -17
- package/src/github.ts +0 -102
- package/src/index.ts +0 -53
- package/src/listen.ts +0 -220
- package/src/log.ts +0 -128
- package/src/middlewares.ts +0 -176
- package/src/misc.ts +0 -149
- package/src/pbkdf2.ts +0 -83
- package/src/perm.ts +0 -194
- package/src/plugins.ts +0 -342
- package/src/serveFile.ts +0 -104
- package/src/serveGuiFiles.ts +0 -95
- package/src/sse.ts +0 -29
- package/src/throttler.ts +0 -106
- package/src/update.ts +0 -67
- package/src/util-files.ts +0 -137
- package/src/util-generators.ts +0 -29
- package/src/util-http.ts +0 -29
- package/src/vfs.ts +0 -258
- package/src/watchLoad.ts +0 -75
- package/src/zip.ts +0 -69
package/src/throttler.js
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// This file is part of HFS - Copyright 2021-2023, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt
|
|
3
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
|
+
};
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.totalInSpeed = exports.totalOutSpeed = exports.totalGot = exports.totalSent = exports.throttler = void 0;
|
|
8
|
+
const stream_1 = require("stream");
|
|
9
|
+
const ThrottledStream_1 = require("./ThrottledStream");
|
|
10
|
+
const config_1 = require("./config");
|
|
11
|
+
const misc_1 = require("./misc");
|
|
12
|
+
const connections_1 = require("./connections");
|
|
13
|
+
const lodash_1 = __importDefault(require("lodash"));
|
|
14
|
+
const events_1 = __importDefault(require("./events"));
|
|
15
|
+
const mainThrottleGroup = new ThrottledStream_1.ThrottleGroup(Infinity);
|
|
16
|
+
(0, config_1.defineConfig)('max_kbps', Infinity).sub(v => mainThrottleGroup.updateLimit(v));
|
|
17
|
+
const ip2group = {};
|
|
18
|
+
const SymThrStr = Symbol('stream');
|
|
19
|
+
const SymTimeout = Symbol('timeout');
|
|
20
|
+
const maxKbpsPerIp = (0, config_1.defineConfig)('max_kbps_per_ip', Infinity);
|
|
21
|
+
const throttler = async (ctx, next) => {
|
|
22
|
+
await next();
|
|
23
|
+
const { body } = ctx;
|
|
24
|
+
if (!body || !(body instanceof stream_1.Readable))
|
|
25
|
+
return;
|
|
26
|
+
// we wrap the stream also for unlimited connections to get speed and other features
|
|
27
|
+
const ipGroup = (0, misc_1.getOrSet)(ip2group, ctx.ip, () => {
|
|
28
|
+
var _a;
|
|
29
|
+
const doLimit = ((_a = ctx.state.account) === null || _a === void 0 ? void 0 : _a.ignore_limits) || (0, misc_1.isLocalHost)(ctx) ? undefined : true;
|
|
30
|
+
const group = new ThrottledStream_1.ThrottleGroup(Infinity, doLimit && mainThrottleGroup);
|
|
31
|
+
const unsub = doLimit && maxKbpsPerIp.sub(v => group.updateLimit(v));
|
|
32
|
+
return { group, count: 0, destroy: unsub };
|
|
33
|
+
});
|
|
34
|
+
const conn = ctx.state.connection;
|
|
35
|
+
if (!conn)
|
|
36
|
+
throw 'assert throttler connection';
|
|
37
|
+
const ts = conn[SymThrStr] = new ThrottledStream_1.ThrottledStream(ipGroup.group, conn[SymThrStr]);
|
|
38
|
+
let closed = false;
|
|
39
|
+
const DELAY = 1000;
|
|
40
|
+
const update = lodash_1.default.debounce(() => {
|
|
41
|
+
const ts = conn[SymThrStr];
|
|
42
|
+
const outSpeed = roundKb(ts.getSpeed());
|
|
43
|
+
(0, connections_1.updateConnection)(conn, { outSpeed, sent: ts.getBytesSent() });
|
|
44
|
+
/* in case this stream stands still for a while (before the end), we'll have neither 'sent' or 'close' events,
|
|
45
|
+
* so who will take care to updateConnection? This artificial next-call will ensure just that */
|
|
46
|
+
clearTimeout(conn[SymTimeout]);
|
|
47
|
+
if (outSpeed || !closed)
|
|
48
|
+
conn[SymTimeout] = setTimeout(update, DELAY);
|
|
49
|
+
}, DELAY, { maxWait: DELAY });
|
|
50
|
+
ts.on('sent', (n) => {
|
|
51
|
+
exports.totalSent += n;
|
|
52
|
+
update();
|
|
53
|
+
});
|
|
54
|
+
++ipGroup.count;
|
|
55
|
+
ts.on('close', () => {
|
|
56
|
+
var _a;
|
|
57
|
+
update.flush();
|
|
58
|
+
closed = true;
|
|
59
|
+
if (--ipGroup.count)
|
|
60
|
+
return; // any left?
|
|
61
|
+
(_a = ipGroup.destroy) === null || _a === void 0 ? void 0 : _a.call(ipGroup);
|
|
62
|
+
delete ip2group[ctx.ip];
|
|
63
|
+
});
|
|
64
|
+
const bak = ctx.response.length; // preserve
|
|
65
|
+
ctx.body = ctx.body.pipe(ts);
|
|
66
|
+
if (bak)
|
|
67
|
+
ctx.response.length = bak;
|
|
68
|
+
};
|
|
69
|
+
exports.throttler = throttler;
|
|
70
|
+
function roundKb(n) {
|
|
71
|
+
return lodash_1.default.round(n, 1) || lodash_1.default.round(n, 3); // further precision if necessary
|
|
72
|
+
}
|
|
73
|
+
exports.totalSent = 0;
|
|
74
|
+
exports.totalGot = 0;
|
|
75
|
+
exports.totalOutSpeed = 0;
|
|
76
|
+
exports.totalInSpeed = 0;
|
|
77
|
+
let lastSent = exports.totalSent;
|
|
78
|
+
let lastGot = exports.totalGot;
|
|
79
|
+
let last = Date.now();
|
|
80
|
+
setInterval(() => {
|
|
81
|
+
const now = Date.now();
|
|
82
|
+
const past = (now - last) / 1000; // seconds
|
|
83
|
+
last = now;
|
|
84
|
+
const deltaSentKb = (exports.totalSent - lastSent) / 1000;
|
|
85
|
+
lastSent = exports.totalSent;
|
|
86
|
+
const deltaGotKb = (exports.totalGot - lastGot) / 1000;
|
|
87
|
+
lastGot = exports.totalGot;
|
|
88
|
+
exports.totalOutSpeed = roundKb(deltaSentKb / past);
|
|
89
|
+
exports.totalInSpeed = roundKb(deltaGotKb / past);
|
|
90
|
+
}, 1000);
|
|
91
|
+
events_1.default.on('connection', (c) => c.socket.on('data', data => exports.totalGot += data.length));
|
package/src/update.js
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// This file is part of HFS - Copyright 2021-2023, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.update = exports.getUpdate = void 0;
|
|
5
|
+
const github_1 = require("./github");
|
|
6
|
+
const const_1 = require("./const");
|
|
7
|
+
const path_1 = require("path");
|
|
8
|
+
const child_process_1 = require("child_process");
|
|
9
|
+
const misc_1 = require("./misc");
|
|
10
|
+
const fs_1 = require("fs");
|
|
11
|
+
const plugins_1 = require("./plugins");
|
|
12
|
+
const promises_1 = require("fs/promises");
|
|
13
|
+
const HFS_REPO = 'rejetto/hfs';
|
|
14
|
+
async function getUpdate() {
|
|
15
|
+
const [latest] = await (0, github_1.getRepoInfo)(HFS_REPO + '/releases?per_page=1');
|
|
16
|
+
if (latest.name === const_1.VERSION)
|
|
17
|
+
throw "you already have the latest version: " + const_1.VERSION;
|
|
18
|
+
return latest;
|
|
19
|
+
}
|
|
20
|
+
exports.getUpdate = getUpdate;
|
|
21
|
+
async function update() {
|
|
22
|
+
if (process.argv0.endsWith('node'))
|
|
23
|
+
throw "only binary versions are supported for now";
|
|
24
|
+
const update = await getUpdate();
|
|
25
|
+
const assetSearch = { win32: 'windows', darwin: 'mac', linux: 'linux' }[process.platform];
|
|
26
|
+
if (!assetSearch)
|
|
27
|
+
throw "this feature doesn't support your platform: " + process.platform;
|
|
28
|
+
const asset = update.assets.find((x) => x.name.endsWith('.zip') && x.name.includes(assetSearch));
|
|
29
|
+
if (!asset)
|
|
30
|
+
throw "asset not found";
|
|
31
|
+
const url = asset.browser_download_url;
|
|
32
|
+
console.log("downloading", url);
|
|
33
|
+
const bin = process.argv[0];
|
|
34
|
+
const binPath = (0, path_1.dirname)(bin);
|
|
35
|
+
const binFile = (0, path_1.basename)(bin);
|
|
36
|
+
const newBinFile = 'new-' + binFile;
|
|
37
|
+
plugins_1.pluginsWatcher.pause();
|
|
38
|
+
try {
|
|
39
|
+
await (0, misc_1.unzip)(await (0, misc_1.httpsStream)(url), path => (0, path_1.join)(binPath, path === binFile ? newBinFile : path));
|
|
40
|
+
const newBin = (0, path_1.join)(binPath, newBinFile);
|
|
41
|
+
if (!const_1.IS_WINDOWS) {
|
|
42
|
+
const { mode } = await (0, promises_1.stat)(bin);
|
|
43
|
+
await (0, promises_1.chmod)(newBin, mode).catch(console.error);
|
|
44
|
+
}
|
|
45
|
+
(0, misc_1.onProcessExit)(() => {
|
|
46
|
+
const oldBinFile = 'old-' + binFile;
|
|
47
|
+
console.log("old version is kept as", oldBinFile);
|
|
48
|
+
const oldBin = (0, path_1.join)(binPath, oldBinFile);
|
|
49
|
+
try {
|
|
50
|
+
(0, fs_1.unlinkSync)(oldBin);
|
|
51
|
+
}
|
|
52
|
+
catch (_a) { }
|
|
53
|
+
(0, fs_1.renameSync)(bin, oldBin);
|
|
54
|
+
console.log("launching new version in background", newBinFile);
|
|
55
|
+
(0, child_process_1.spawn)(newBin, ['--updating', binFile], { detached: true, shell: true })
|
|
56
|
+
.on('error', console.error);
|
|
57
|
+
});
|
|
58
|
+
console.log('quitting');
|
|
59
|
+
process.exit();
|
|
60
|
+
}
|
|
61
|
+
catch (_a) {
|
|
62
|
+
plugins_1.pluginsWatcher.unpause();
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
exports.update = update;
|
|
66
|
+
if (const_1.argv.updating) { // we were launched with a temporary name, restore original name to avoid breaking references
|
|
67
|
+
const bin = process.argv[0];
|
|
68
|
+
(0, fs_1.renameSync)(bin, (0, path_1.join)((0, path_1.dirname)(bin), const_1.argv.updating));
|
|
69
|
+
console.log("renamed binary file to", const_1.argv.updating);
|
|
70
|
+
}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// This file is part of HFS - Copyright 2021-2023, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt
|
|
3
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
|
+
};
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.prepareFolder = exports.unzip = exports.run = exports.dirStream = exports.adjustStaticPathForGlob = exports.isWindowsDrive = exports.dirTraversal = exports.watchDir = exports.readFileBusy = exports.isFile = exports.isDirectory = void 0;
|
|
8
|
+
const promises_1 = __importDefault(require("fs/promises"));
|
|
9
|
+
const misc_1 = require("./misc");
|
|
10
|
+
const fs_1 = require("fs");
|
|
11
|
+
const path_1 = require("path");
|
|
12
|
+
const fast_glob_1 = __importDefault(require("fast-glob"));
|
|
13
|
+
const const_1 = require("./const");
|
|
14
|
+
const child_process_1 = require("child_process");
|
|
15
|
+
const stream_1 = require("stream");
|
|
16
|
+
// @ts-ignore
|
|
17
|
+
const unzip_stream_1 = __importDefault(require("unzip-stream"));
|
|
18
|
+
async function isDirectory(path) {
|
|
19
|
+
try {
|
|
20
|
+
return (await promises_1.default.stat(path)).isDirectory();
|
|
21
|
+
}
|
|
22
|
+
catch (_a) {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
exports.isDirectory = isDirectory;
|
|
27
|
+
async function isFile(path) {
|
|
28
|
+
try {
|
|
29
|
+
return (await promises_1.default.stat(path)).isFile();
|
|
30
|
+
}
|
|
31
|
+
catch (_a) {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
exports.isFile = isFile;
|
|
36
|
+
async function readFileBusy(path) {
|
|
37
|
+
return promises_1.default.readFile(path, 'utf8').catch(e => {
|
|
38
|
+
if ((e === null || e === void 0 ? void 0 : e.code) !== 'EBUSY')
|
|
39
|
+
throw e;
|
|
40
|
+
console.debug('busy');
|
|
41
|
+
return (0, misc_1.wait)(100).then(() => readFileBusy(path));
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
exports.readFileBusy = readFileBusy;
|
|
45
|
+
function watchDir(dir, cb) {
|
|
46
|
+
let watcher;
|
|
47
|
+
let paused = false;
|
|
48
|
+
try {
|
|
49
|
+
watcher = (0, fs_1.watch)(dir, controlledCb);
|
|
50
|
+
}
|
|
51
|
+
catch (_a) {
|
|
52
|
+
// failing watching the content of the dir, we try to monitor its parent, but filtering events only for our target dir
|
|
53
|
+
const base = (0, path_1.basename)(dir);
|
|
54
|
+
try {
|
|
55
|
+
watcher = (0, fs_1.watch)((0, path_1.dirname)(dir), (event, name) => {
|
|
56
|
+
if (name !== base)
|
|
57
|
+
return;
|
|
58
|
+
try {
|
|
59
|
+
watcher.close(); // if we succeed, we give up the parent watching
|
|
60
|
+
watcher = (0, fs_1.watch)(dir, controlledCb); // attempt at passing to a more specific watching
|
|
61
|
+
}
|
|
62
|
+
catch (_a) { }
|
|
63
|
+
controlledCb();
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
catch (e) {
|
|
67
|
+
console.debug(String(e));
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return {
|
|
71
|
+
working() { return Boolean(watcher); },
|
|
72
|
+
stop() { watcher === null || watcher === void 0 ? void 0 : watcher.close(); },
|
|
73
|
+
pause() { paused = true; },
|
|
74
|
+
unpause() { paused = false; },
|
|
75
|
+
};
|
|
76
|
+
function controlledCb() {
|
|
77
|
+
if (!paused)
|
|
78
|
+
cb();
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
exports.watchDir = watchDir;
|
|
82
|
+
function dirTraversal(s) {
|
|
83
|
+
return s && /(^|[/\\])\.\.($|[/\\])/.test(s);
|
|
84
|
+
}
|
|
85
|
+
exports.dirTraversal = dirTraversal;
|
|
86
|
+
function isWindowsDrive(s) {
|
|
87
|
+
return s && /^[a-zA-Z]:$/.test(s);
|
|
88
|
+
}
|
|
89
|
+
exports.isWindowsDrive = isWindowsDrive;
|
|
90
|
+
// apply this to paths that may contain \ as separator (not supported by fast-glob) and other special chars to be escaped (parenthesis)
|
|
91
|
+
function adjustStaticPathForGlob(path) {
|
|
92
|
+
return fast_glob_1.default.escapePath(path.replace(/\\/g, '/'));
|
|
93
|
+
}
|
|
94
|
+
exports.adjustStaticPathForGlob = adjustStaticPathForGlob;
|
|
95
|
+
async function* dirStream(path, deep) {
|
|
96
|
+
if (!await isDirectory(path))
|
|
97
|
+
throw Error('ENOTDIR');
|
|
98
|
+
const dirStream = fast_glob_1.default.stream(deep ? '**/*' : '*', {
|
|
99
|
+
cwd: path,
|
|
100
|
+
dot: true,
|
|
101
|
+
deep,
|
|
102
|
+
onlyFiles: false,
|
|
103
|
+
suppressErrors: true,
|
|
104
|
+
objectMode: true,
|
|
105
|
+
});
|
|
106
|
+
const skip = await getItemsToSkip(path);
|
|
107
|
+
for await (const entry of dirStream) {
|
|
108
|
+
let { path, dirent } = entry;
|
|
109
|
+
if (!dirent.isDirectory() && !dirent.isFile())
|
|
110
|
+
continue;
|
|
111
|
+
path = String(path);
|
|
112
|
+
if (!(skip === null || skip === void 0 ? void 0 : skip.includes(path)))
|
|
113
|
+
yield path;
|
|
114
|
+
}
|
|
115
|
+
async function getItemsToSkip(path) {
|
|
116
|
+
if (!const_1.IS_WINDOWS)
|
|
117
|
+
return;
|
|
118
|
+
const out = await run('dir', ['/ah', '/b', path.replace(/\//g, '\\')])
|
|
119
|
+
.catch(() => ''); // error in case of no matching file
|
|
120
|
+
return out.split('\r\n').slice(0, -1);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
exports.dirStream = dirStream;
|
|
124
|
+
function run(cmd, args = []) {
|
|
125
|
+
return new Promise((resolve, reject) => (0, child_process_1.execFile)('cmd', ['/c', cmd, ...args], (err, stdout) => {
|
|
126
|
+
if (err)
|
|
127
|
+
reject(err);
|
|
128
|
+
else
|
|
129
|
+
resolve(stdout);
|
|
130
|
+
}));
|
|
131
|
+
}
|
|
132
|
+
exports.run = run;
|
|
133
|
+
async function unzip(stream, cb) {
|
|
134
|
+
let pending = Promise.resolve();
|
|
135
|
+
return new Promise(resolve => stream.pipe(unzip_stream_1.default.Parse())
|
|
136
|
+
.on('end', () => pending.then(resolve))
|
|
137
|
+
.on('entry', async (entry) => {
|
|
138
|
+
const { path, type } = entry;
|
|
139
|
+
const dest = cb(path);
|
|
140
|
+
if (!dest || type !== 'File')
|
|
141
|
+
return entry.autodrain();
|
|
142
|
+
await pending; // don't overlap writings
|
|
143
|
+
console.debug('unzip', dest);
|
|
144
|
+
await prepareFolder(dest);
|
|
145
|
+
const thisFile = entry.pipe((0, fs_1.createWriteStream)(dest));
|
|
146
|
+
pending = (0, stream_1.once)(thisFile, 'finish');
|
|
147
|
+
}));
|
|
148
|
+
}
|
|
149
|
+
exports.unzip = unzip;
|
|
150
|
+
async function prepareFolder(path, dirnameIt = true) {
|
|
151
|
+
if (dirnameIt)
|
|
152
|
+
path = (0, path_1.dirname)(path);
|
|
153
|
+
if (isWindowsDrive(path))
|
|
154
|
+
return;
|
|
155
|
+
try {
|
|
156
|
+
await promises_1.default.mkdir(path, { recursive: true });
|
|
157
|
+
return true;
|
|
158
|
+
}
|
|
159
|
+
catch (_a) {
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
exports.prepareFolder = prepareFolder;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// This file is part of HFS - Copyright 2021-2023, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.asyncGeneratorToReadable = exports.asyncGeneratorToArray = exports.filterMapGenerator = void 0;
|
|
5
|
+
// callback can return undefined to skip element
|
|
6
|
+
const stream_1 = require("stream");
|
|
7
|
+
async function* filterMapGenerator(generator, filterMap) {
|
|
8
|
+
for await (const x of generator) {
|
|
9
|
+
const res = await filterMap(x);
|
|
10
|
+
if (res !== undefined)
|
|
11
|
+
yield res;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
exports.filterMapGenerator = filterMapGenerator;
|
|
15
|
+
async function asyncGeneratorToArray(generator) {
|
|
16
|
+
const ret = [];
|
|
17
|
+
for await (const x of generator)
|
|
18
|
+
ret.push(x);
|
|
19
|
+
return ret;
|
|
20
|
+
}
|
|
21
|
+
exports.asyncGeneratorToArray = asyncGeneratorToArray;
|
|
22
|
+
function asyncGeneratorToReadable(generator) {
|
|
23
|
+
const iterator = generator[Symbol.asyncIterator]();
|
|
24
|
+
return new stream_1.Readable({
|
|
25
|
+
objectMode: true,
|
|
26
|
+
read() {
|
|
27
|
+
iterator.next().then(it => this.push(it.done ? null : it.value));
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
exports.asyncGeneratorToReadable = asyncGeneratorToReadable;
|
package/src/util-http.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// This file is part of HFS - Copyright 2021-2023, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt
|
|
3
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
|
+
};
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.httpsStream = exports.httpsString = void 0;
|
|
8
|
+
const node_https_1 = __importDefault(require("node:https"));
|
|
9
|
+
const const_1 = require("./const");
|
|
10
|
+
function httpsString(url, options = {}) {
|
|
11
|
+
return httpsStream(url, options).then(res => new Promise(resolve => {
|
|
12
|
+
let buf = '';
|
|
13
|
+
res.on('data', chunk => buf += chunk.toString());
|
|
14
|
+
res.on('end', () => resolve(Object.assign(res, {
|
|
15
|
+
ok: (res.statusCode || 400) < 400,
|
|
16
|
+
body: buf
|
|
17
|
+
})));
|
|
18
|
+
}));
|
|
19
|
+
}
|
|
20
|
+
exports.httpsString = httpsString;
|
|
21
|
+
function httpsStream(url, options = {}) {
|
|
22
|
+
return new Promise((resolve, reject) => {
|
|
23
|
+
node_https_1.default.request(url, options, res => {
|
|
24
|
+
if (!res.statusCode || res.statusCode >= 400)
|
|
25
|
+
throw res;
|
|
26
|
+
if (res.statusCode === const_1.HTTP_TEMPORARY_REDIRECT && res.headers.location)
|
|
27
|
+
return resolve(httpsStream(res.headers.location, options));
|
|
28
|
+
resolve(res);
|
|
29
|
+
}).on('error', reject).end();
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
exports.httpsStream = httpsStream;
|
package/src/vfs.js
ADDED
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// This file is part of HFS - Copyright 2021-2023, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt
|
|
3
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
|
+
};
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.cantReadStatusCode = exports.walkNode = exports.hasPermission = exports.nodeIsDirectory = exports.getNodeName = exports.saveVfs = exports.vfs = exports.urlToNode = exports.MIME_AUTO = exports.defaultPerms = void 0;
|
|
8
|
+
const promises_1 = __importDefault(require("fs/promises"));
|
|
9
|
+
const path_1 = require("path");
|
|
10
|
+
const micromatch_1 = require("micromatch");
|
|
11
|
+
const misc_1 = require("./misc");
|
|
12
|
+
const lodash_1 = __importDefault(require("lodash"));
|
|
13
|
+
const config_1 = require("./config");
|
|
14
|
+
const const_1 = require("./const");
|
|
15
|
+
const events_1 = __importDefault(require("./events"));
|
|
16
|
+
const perm_1 = require("./perm");
|
|
17
|
+
const misc_2 = require("./misc");
|
|
18
|
+
const WHO_ANYONE = true;
|
|
19
|
+
const WHO_NO_ONE = false;
|
|
20
|
+
const WHO_ANY_ACCOUNT = '*';
|
|
21
|
+
exports.defaultPerms = {
|
|
22
|
+
can_see: WHO_ANYONE,
|
|
23
|
+
can_read: WHO_ANYONE,
|
|
24
|
+
can_upload: WHO_NO_ONE,
|
|
25
|
+
};
|
|
26
|
+
exports.MIME_AUTO = 'auto';
|
|
27
|
+
function inheritFromParent(parent, child) {
|
|
28
|
+
var _a;
|
|
29
|
+
for (const k of (0, misc_1.typedKeys)(exports.defaultPerms)) {
|
|
30
|
+
const v = parent[k];
|
|
31
|
+
if (v !== undefined)
|
|
32
|
+
(_a = child[k]) !== null && _a !== void 0 ? _a : (child[k] = v);
|
|
33
|
+
}
|
|
34
|
+
if (typeof parent.mime === 'object' && typeof child.mime === 'object')
|
|
35
|
+
lodash_1.default.defaults(child.mime, parent.mime);
|
|
36
|
+
else
|
|
37
|
+
child.mime || (child.mime = parent.mime);
|
|
38
|
+
return child;
|
|
39
|
+
}
|
|
40
|
+
async function urlToNode(url, ctx, parent = exports.vfs, getRest) {
|
|
41
|
+
var _a;
|
|
42
|
+
let initialSlashes = 0;
|
|
43
|
+
while (url[initialSlashes] === '/')
|
|
44
|
+
initialSlashes++;
|
|
45
|
+
let nextSlash = url.indexOf('/', initialSlashes);
|
|
46
|
+
const name = decodeURIComponent(url.slice(initialSlashes, nextSlash < 0 ? undefined : nextSlash));
|
|
47
|
+
if (!name)
|
|
48
|
+
return parent;
|
|
49
|
+
const rest = nextSlash < 0 ? '' : url.slice(nextSlash + 1, url.endsWith('/') ? -1 : undefined);
|
|
50
|
+
if ((0, misc_1.dirTraversal)(name) || /[\/]/.test(name)) {
|
|
51
|
+
if (ctx)
|
|
52
|
+
ctx.status = const_1.HTTP_FOOL;
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
// does the tree node have a child that goes by this name?
|
|
56
|
+
const sameName = !const_1.IS_WINDOWS ? (x) => x === name // easy
|
|
57
|
+
: (0, misc_2.with_)(name.toLowerCase(), lc => (x) => x.toLowerCase() === lc);
|
|
58
|
+
const child = (_a = parent.children) === null || _a === void 0 ? void 0 : _a.find(x => sameName(getNodeName(x)));
|
|
59
|
+
const ret = {
|
|
60
|
+
...child,
|
|
61
|
+
original: child,
|
|
62
|
+
isTemp: true,
|
|
63
|
+
};
|
|
64
|
+
inheritFromParent(parent, ret);
|
|
65
|
+
inheritMasks(ret, parent, name);
|
|
66
|
+
applyMasks(ret, parent, name);
|
|
67
|
+
if (child) // yes
|
|
68
|
+
return urlToNode(rest, ctx, ret, getRest);
|
|
69
|
+
// not in the tree, we can see consider continuing on the disk
|
|
70
|
+
if (!parent.source)
|
|
71
|
+
return; // but then we need the current node to be linked to the disk, otherwise, we give up
|
|
72
|
+
let onDisk = name;
|
|
73
|
+
if (parent.rename) { // reverse the mapping
|
|
74
|
+
for (const [from, to] of Object.entries(parent.rename))
|
|
75
|
+
if (name === to) {
|
|
76
|
+
onDisk = from;
|
|
77
|
+
break; // found, search no more
|
|
78
|
+
}
|
|
79
|
+
ret.rename = renameUnderPath(parent.rename, name);
|
|
80
|
+
}
|
|
81
|
+
ret.source = (0, misc_1.enforceFinal)('/', parent.source) + onDisk;
|
|
82
|
+
if (parent.default)
|
|
83
|
+
inheritFromParent({ mime: { '*': exports.MIME_AUTO } }, ret);
|
|
84
|
+
if (rest)
|
|
85
|
+
return urlToNode(rest, ctx, ret, getRest);
|
|
86
|
+
if (ret.source)
|
|
87
|
+
try {
|
|
88
|
+
await promises_1.default.stat(ret.source);
|
|
89
|
+
} // check existence
|
|
90
|
+
catch (_b) {
|
|
91
|
+
if (!getRest)
|
|
92
|
+
return;
|
|
93
|
+
getRest(onDisk);
|
|
94
|
+
return parent;
|
|
95
|
+
}
|
|
96
|
+
return ret;
|
|
97
|
+
}
|
|
98
|
+
exports.urlToNode = urlToNode;
|
|
99
|
+
exports.vfs = {};
|
|
100
|
+
(0, config_1.defineConfig)('vfs', {}).sub(data => exports.vfs = data);
|
|
101
|
+
function saveVfs() {
|
|
102
|
+
return (0, config_1.setConfig)({ vfs: lodash_1.default.cloneDeep(exports.vfs) }, true);
|
|
103
|
+
}
|
|
104
|
+
exports.saveVfs = saveVfs;
|
|
105
|
+
function getNodeName(node) {
|
|
106
|
+
return node.name
|
|
107
|
+
|| node.source && (/^[a-zA-Z]:\\?$/.test(node.source) && node.source.slice(0, 2)
|
|
108
|
+
|| (0, path_1.basename)(node.source)
|
|
109
|
+
|| node.source)
|
|
110
|
+
|| ''; // should happen only for root
|
|
111
|
+
}
|
|
112
|
+
exports.getNodeName = getNodeName;
|
|
113
|
+
async function nodeIsDirectory(node) {
|
|
114
|
+
return Boolean(!node.source || await (0, misc_1.isDirectory)(node.source));
|
|
115
|
+
}
|
|
116
|
+
exports.nodeIsDirectory = nodeIsDirectory;
|
|
117
|
+
function hasPermission(node, perm, ctx) {
|
|
118
|
+
var _a;
|
|
119
|
+
return matchWho((_a = node[perm]) !== null && _a !== void 0 ? _a : exports.defaultPerms[perm], ctx)
|
|
120
|
+
&& (perm !== 'can_see' || hasPermission(node, 'can_read', ctx)); // can_see is used to hide something you nonetheless can_read, so you MUST also can_read
|
|
121
|
+
}
|
|
122
|
+
exports.hasPermission = hasPermission;
|
|
123
|
+
async function* walkNode(parent, ctx, depth = 0, prefixPath = '') {
|
|
124
|
+
var _a;
|
|
125
|
+
const { children, source } = parent;
|
|
126
|
+
const took = prefixPath ? undefined : new Set();
|
|
127
|
+
if (children)
|
|
128
|
+
for (let idx = 0; idx < children.length; idx++) {
|
|
129
|
+
const child = children[idx];
|
|
130
|
+
const name = prefixPath + getNodeName(child);
|
|
131
|
+
took === null || took === void 0 ? void 0 : took.add(name);
|
|
132
|
+
yield* workItem({
|
|
133
|
+
...child,
|
|
134
|
+
name,
|
|
135
|
+
}, depth > 0 && await nodeIsDirectory(child).catch(() => false));
|
|
136
|
+
}
|
|
137
|
+
if (!source)
|
|
138
|
+
return;
|
|
139
|
+
try {
|
|
140
|
+
for await (const path of (0, misc_1.dirStream)(source, depth)) {
|
|
141
|
+
if (ctx === null || ctx === void 0 ? void 0 : ctx.req.aborted)
|
|
142
|
+
return;
|
|
143
|
+
const name = prefixPath + (((_a = parent.rename) === null || _a === void 0 ? void 0 : _a[path]) || path);
|
|
144
|
+
if (took === null || took === void 0 ? void 0 : took.has(name))
|
|
145
|
+
continue;
|
|
146
|
+
yield* workItem({
|
|
147
|
+
name,
|
|
148
|
+
source: (0, path_1.join)(source, path),
|
|
149
|
+
rename: renameUnderPath(parent.rename, path),
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
catch (e) {
|
|
154
|
+
console.debug('glob', source, e); // ENOTDIR, or lacking permissions
|
|
155
|
+
}
|
|
156
|
+
// item will be changed, so be sure to pass a temp node
|
|
157
|
+
async function* workItem(item, recur = false) {
|
|
158
|
+
const name = getNodeName(item);
|
|
159
|
+
// we basename for depth>0 where we already have the rest of the path in the parent's url, and would be duplicated
|
|
160
|
+
const virtualBasename = (0, path_1.basename)(name);
|
|
161
|
+
item.isTemp = true;
|
|
162
|
+
inheritFromParent(parent, item);
|
|
163
|
+
applyMasks(item, parent, virtualBasename);
|
|
164
|
+
if (ctx && !hasPermission(item, 'can_see', ctx))
|
|
165
|
+
return;
|
|
166
|
+
yield item;
|
|
167
|
+
if (!recur)
|
|
168
|
+
return;
|
|
169
|
+
inheritMasks(item, parent, virtualBasename);
|
|
170
|
+
yield* walkNode(item, ctx, depth - 1, name + '/');
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
exports.walkNode = walkNode;
|
|
174
|
+
function applyMasks(item, parent, virtualBasename) {
|
|
175
|
+
const { masks } = parent;
|
|
176
|
+
if (!masks)
|
|
177
|
+
return;
|
|
178
|
+
for (const k in masks)
|
|
179
|
+
if (k.startsWith('**/') && (0, micromatch_1.isMatch)(virtualBasename, k.slice(3))
|
|
180
|
+
|| !k.includes('/') && (0, micromatch_1.isMatch)(virtualBasename, k))
|
|
181
|
+
Object.assign(item, masks[k]);
|
|
182
|
+
}
|
|
183
|
+
function inheritMasks(item, parent, virtualBasename) {
|
|
184
|
+
const { masks } = parent;
|
|
185
|
+
if (!masks)
|
|
186
|
+
return;
|
|
187
|
+
const o = {};
|
|
188
|
+
for (const k in masks)
|
|
189
|
+
if (k.startsWith('**/'))
|
|
190
|
+
o[k.slice(3)] = masks[k];
|
|
191
|
+
else if (k.startsWith(virtualBasename + '/'))
|
|
192
|
+
o[k.slice(virtualBasename.length + 1)] = masks[k];
|
|
193
|
+
if (Object.keys(o).length)
|
|
194
|
+
item.masks = o;
|
|
195
|
+
}
|
|
196
|
+
function renameUnderPath(rename, path) {
|
|
197
|
+
if (!rename)
|
|
198
|
+
return rename;
|
|
199
|
+
const match = path + '/';
|
|
200
|
+
rename = Object.fromEntries(Object.entries(rename).map(([k, v]) => [k.startsWith(match) ? k.slice(match.length) : '', v]));
|
|
201
|
+
delete rename[''];
|
|
202
|
+
return lodash_1.default.isEmpty(rename) ? undefined : rename;
|
|
203
|
+
}
|
|
204
|
+
function matchWho(who, ctx) {
|
|
205
|
+
return who === WHO_ANYONE
|
|
206
|
+
|| who === WHO_ANY_ACCOUNT && Boolean(ctx.state.account)
|
|
207
|
+
|| Array.isArray(who) && (() => // check if I or any ancestor match `who`, but cache ancestors' usernames inside context state
|
|
208
|
+
(0, misc_1.getOrSet)(ctx.state, 'usernames', () => (0, perm_1.getCurrentUsernameExpanded)(ctx)).some((u) => who.includes(u)))();
|
|
209
|
+
}
|
|
210
|
+
function cantReadStatusCode(node) {
|
|
211
|
+
return node.can_read === false ? const_1.HTTP_FORBIDDEN : const_1.HTTP_UNAUTHORIZED;
|
|
212
|
+
}
|
|
213
|
+
exports.cantReadStatusCode = cantReadStatusCode;
|
|
214
|
+
events_1.default.on('accountRenamed', (from, to) => {
|
|
215
|
+
recur(exports.vfs);
|
|
216
|
+
saveVfs();
|
|
217
|
+
function recur(n) {
|
|
218
|
+
var _a;
|
|
219
|
+
for (const k of (0, misc_1.typedKeys)(exports.defaultPerms))
|
|
220
|
+
replace(n[k]);
|
|
221
|
+
if (n.masks)
|
|
222
|
+
Object.values(n.masks).forEach(recur);
|
|
223
|
+
(_a = n.children) === null || _a === void 0 ? void 0 : _a.forEach(recur);
|
|
224
|
+
}
|
|
225
|
+
function replace(a) {
|
|
226
|
+
if (!Array.isArray(a))
|
|
227
|
+
return;
|
|
228
|
+
for (let i = 0; i < a.length; i++)
|
|
229
|
+
if (a[i] === from)
|
|
230
|
+
a[i] = to;
|
|
231
|
+
}
|
|
232
|
+
});
|