hfs 0.42.3 → 0.44.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 +56 -17
- package/admin/assets/index-35f6e3dc.css +1 -0
- package/admin/assets/index-ef68a7ab.js +510 -0
- package/{frontend/assets/sha512-f6798733.js → admin/assets/sha512-d091720e.js} +1 -1
- package/admin/index.html +2 -2
- package/frontend/assets/index-a3b0d6ac.js +94 -0
- package/frontend/assets/index-fe0f3d77.css +1 -0
- package/{admin/assets/sha512-69b26793.js → frontend/assets/sha512-96fd938f.js} +1 -1
- package/frontend/fontello.css +9 -3
- package/frontend/fontello.woff2 +0 -0
- package/frontend/index.html +3 -2
- package/package.json +2 -2
- package/plugins/antibrute/plugin.js +1 -1
- package/plugins/download-counter/plugin.js +14 -5
- package/plugins/download-counter/public/main.js +12 -2
- package/plugins/vhosting/plugin.js +17 -11
- package/src/adminApis.js +4 -4
- package/src/api.auth.js +4 -1
- package/src/api.file_list.js +22 -10
- package/src/api.lang.js +9 -11
- package/src/api.vfs.js +43 -29
- package/src/apiMiddleware.js +3 -2
- package/src/block.js +6 -20
- package/src/config.js +6 -2
- package/src/const.js +5 -4
- package/src/customHtml.js +2 -2
- package/src/debounceAsync.js +3 -3
- package/src/frontEndApis.js +7 -32
- package/src/github.js +26 -8
- package/src/index.js +2 -1
- package/src/lang.js +67 -0
- package/src/langs/embedded.js +13 -0
- package/src/langs/hfs-lang-it.json +100 -0
- package/src/langs/hfs-lang-ko.json +103 -0
- package/src/langs/hfs-lang-ms.json +70 -0
- package/src/langs/hfs-lang-ru.json +106 -0
- package/src/langs/hfs-lang-sr.json +108 -0
- package/src/langs/hfs-lang-zh-tw.json +106 -0
- package/src/langs/hfs-lang-zh.json +98 -0
- package/src/listen.js +8 -3
- package/src/log.js +6 -2
- package/src/middlewares.js +24 -18
- package/src/misc.js +17 -9
- package/src/perm.js +1 -1
- package/src/plugins.js +6 -6
- package/src/serveFile.js +2 -2
- package/src/serveGuiFiles.js +23 -10
- package/src/update.js +1 -2
- package/src/upload.js +6 -6
- package/src/util-http.js +5 -3
- package/src/vfs.js +114 -74
- package/src/zip.js +4 -4
- package/admin/assets/index-08017e15.js +0 -511
- package/admin/assets/index-94bbe0be.css +0 -1
- package/frontend/assets/index-5f125477.js +0 -94
- package/frontend/assets/index-a09cacfd.css +0 -1
package/src/perm.js
CHANGED
|
@@ -41,7 +41,7 @@ function saveSrpInfo(account, salt, verifier) {
|
|
|
41
41
|
account.srp = String(salt) + '|' + String(verifier);
|
|
42
42
|
}
|
|
43
43
|
exports.saveSrpInfo = saveSrpInfo;
|
|
44
|
-
exports.allowClearTextLogin = (0, config_1.defineConfig)('allow_clear_text_login');
|
|
44
|
+
exports.allowClearTextLogin = (0, config_1.defineConfig)('allow_clear_text_login', false);
|
|
45
45
|
const srp6aNimbusRoutines = new tssrp6a_1.SRPRoutines(new tssrp6a_1.SRPParameters());
|
|
46
46
|
async function updateAccount(account, changer) {
|
|
47
47
|
const was = JSON.stringify(account);
|
package/src/plugins.js
CHANGED
|
@@ -231,7 +231,8 @@ async function rescan() {
|
|
|
231
231
|
if (found.includes(id)) // not twice
|
|
232
232
|
continue;
|
|
233
233
|
found.push(id);
|
|
234
|
-
|
|
234
|
+
if (!plugins[id]) // already loaded
|
|
235
|
+
loadPlugin(id, f);
|
|
235
236
|
}
|
|
236
237
|
for (const [id, p] of Object.entries(foundDisabled)) {
|
|
237
238
|
const a = availablePlugins[id];
|
|
@@ -322,17 +323,16 @@ function deleteModule(id) {
|
|
|
322
323
|
for (const child of (0, misc_1.wantArray)((_a = cache[k]) === null || _a === void 0 ? void 0 : _a.children))
|
|
323
324
|
(0, misc_1.getOrSet)(requiredBy, child.id, () => []).push(k);
|
|
324
325
|
const deleted = [];
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
let mod = cache[id];
|
|
326
|
+
(function deleteCache(id) {
|
|
327
|
+
const mod = cache[id];
|
|
328
328
|
if (!mod)
|
|
329
329
|
return;
|
|
330
330
|
delete cache[id];
|
|
331
331
|
deleted.push(id);
|
|
332
332
|
for (const child of mod.children)
|
|
333
333
|
if (!lodash_1.default.difference(requiredBy[child.id], deleted).length)
|
|
334
|
-
|
|
335
|
-
}
|
|
334
|
+
deleteCache(child.id);
|
|
335
|
+
})(id);
|
|
336
336
|
}
|
|
337
337
|
(0, misc_1.onProcessExit)(() => Promise.allSettled(mapPlugins(pl => pl.unload())));
|
|
338
338
|
function parsePluginSource(id, source) {
|
package/src/serveFile.js
CHANGED
|
@@ -33,6 +33,8 @@ function serveFileNode(ctx, node) {
|
|
|
33
33
|
}
|
|
34
34
|
}
|
|
35
35
|
ctx.vfsNode = node; // useful to tell service files from files shared by the user
|
|
36
|
+
if ('dl' in ctx.query) // please, download
|
|
37
|
+
ctx.attachment(name);
|
|
36
38
|
return serveFile(ctx, source || '', mimeString);
|
|
37
39
|
}
|
|
38
40
|
exports.serveFileNode = serveFileNode;
|
|
@@ -41,8 +43,6 @@ async function serveFile(ctx, source, mime, content) {
|
|
|
41
43
|
if (!source)
|
|
42
44
|
return;
|
|
43
45
|
const fn = path_1.default.basename(source);
|
|
44
|
-
if ('dl' in ctx.params) // please, download
|
|
45
|
-
ctx.attachment(fn);
|
|
46
46
|
mime = mime !== null && mime !== void 0 ? mime : lodash_1.default.find(mimeCfg.get(), (v, k) => (0, misc_1.matches)(fn, k));
|
|
47
47
|
if (mime === vfs_1.MIME_AUTO)
|
|
48
48
|
mime = mime_types_1.default.lookup(source) || '';
|
package/src/serveGuiFiles.js
CHANGED
|
@@ -41,6 +41,7 @@ const valtio_1 = require("valtio");
|
|
|
41
41
|
const customHtml_1 = require("./customHtml");
|
|
42
42
|
const lodash_1 = __importDefault(require("lodash"));
|
|
43
43
|
const config_1 = require("./config");
|
|
44
|
+
const lang_1 = require("./lang");
|
|
44
45
|
// in case of dev env we have our static files within the 'dist' folder'
|
|
45
46
|
const DEV_STATIC = process.env.DEV ? 'dist/' : '';
|
|
46
47
|
function serveStatic(uri) {
|
|
@@ -68,8 +69,6 @@ function serveStatic(uri) {
|
|
|
68
69
|
return (0, serveFile_1.serveFile)(ctx, fullPath, 'auto', content);
|
|
69
70
|
// we don't cache the index as it's small and may prevent plugins change to apply
|
|
70
71
|
ctx.body = await treatIndex(ctx, uri, String(content));
|
|
71
|
-
ctx.type = 'html';
|
|
72
|
-
ctx.set('Cache-Control', 'no-store, no-cache, must-revalidate');
|
|
73
72
|
};
|
|
74
73
|
}
|
|
75
74
|
function shouldServeApp(ctx) {
|
|
@@ -84,10 +83,10 @@ function adjustBundlerLinks(ctx, uri, data) {
|
|
|
84
83
|
async function treatIndex(ctx, filesUri, body) {
|
|
85
84
|
const session = await (0, api_auth_1.refresh_session)({}, ctx);
|
|
86
85
|
ctx.set('etag', '');
|
|
86
|
+
ctx.set('Cache-Control', 'no-store, no-cache, must-revalidate');
|
|
87
|
+
ctx.type = 'html';
|
|
87
88
|
const isFrontend = filesUri === const_1.FRONTEND_URI;
|
|
88
89
|
const pub = ctx.state.revProxyPath + const_1.PLUGINS_PUB_URI;
|
|
89
|
-
const css = (0, plugins_1.mapPlugins)((plug, k) => { var _a; return (_a = (isFrontend ? plug.frontend_css : null)) === null || _a === void 0 ? void 0 : _a.map(f => pub + k + '/' + f); }).flat().filter(Boolean);
|
|
90
|
-
const js = (0, plugins_1.mapPlugins)((plug, k) => { var _a; return (_a = (isFrontend ? plug.frontend_js : null)) === null || _a === void 0 ? void 0 : _a.map(f => pub + k + '/' + f); }).flat().filter(Boolean);
|
|
91
90
|
// expose plugins' configs that are declared with 'frontend' attribute
|
|
92
91
|
const plugins = Object.fromEntries((0, misc_1.onlyTruthy)((0, plugins_1.mapPlugins)((pl, name) => {
|
|
93
92
|
var _a, _b;
|
|
@@ -99,9 +98,10 @@ async function treatIndex(ctx, filesUri, body) {
|
|
|
99
98
|
configs = ((_b = (_a = (0, plugins_1.getPluginInfo)(name)).onFrontendConfig) === null || _b === void 0 ? void 0 : _b.call(_a, configs)) || configs;
|
|
100
99
|
return !lodash_1.default.isEmpty(configs) && [name, configs];
|
|
101
100
|
})));
|
|
101
|
+
const lang = await (0, lang_1.getLangData)(ctx);
|
|
102
102
|
let ret = body
|
|
103
103
|
.replace(/((?:src|href) *= *['"])\/?(?![a-z]+:\/\/)/g, '$1' + ctx.state.revProxyPath + filesUri)
|
|
104
|
-
.replace('
|
|
104
|
+
.replace('<body>', () => `<body>
|
|
105
105
|
${!isFrontend ? '' : `
|
|
106
106
|
<title>${adminApis_1.title.get()}</title>
|
|
107
107
|
<link rel="icon" href="${adminApis_1.favicon.get() ? '/favicon.ico' : 'data:;'}" />
|
|
@@ -115,18 +115,31 @@ async function treatIndex(ctx, filesUri, body) {
|
|
|
115
115
|
plugins,
|
|
116
116
|
prefixUrl: ctx.state.revProxyPath,
|
|
117
117
|
customHtml: lodash_1.default.omit(Object.fromEntries(customHtml_1.customHtmlState.sections), ['top', 'bottom']),
|
|
118
|
-
fileMenuOnLink: fileMenuOnLink.get()
|
|
119
|
-
|
|
118
|
+
fileMenuOnLink: fileMenuOnLink.get(),
|
|
119
|
+
lang
|
|
120
|
+
}, null, 4)
|
|
121
|
+
.replace(/<(\/script)/g, '<"+"$1') /*avoid breaking our script container*/}
|
|
120
122
|
document.documentElement.setAttribute('ver', '${const_1.VERSION.split('-')[0] /*for style selectors*/}')
|
|
123
|
+
HFS.getPluginKey = () => document.currentScript?.getAttribute('plugin')
|
|
124
|
+
|| console.error("this function must be called at the very top of your file")
|
|
125
|
+
HFS.getPluginConfig = () => HFS.plugins[HFS.getPluginKey()]
|
|
121
126
|
</script>
|
|
122
127
|
<style>
|
|
123
128
|
:root {
|
|
124
129
|
${lodash_1.default.map(plugins, (configs, pluginName) => lodash_1.default.map(configs, (v, k) => `--${pluginName}-${k}: ${serializeCss(v)};`).join('\n')).join('')}
|
|
125
130
|
}
|
|
126
131
|
</style>
|
|
127
|
-
${
|
|
128
|
-
|
|
129
|
-
|
|
132
|
+
${!isFrontend ? '' : (0, plugins_1.mapPlugins)((plug, k) => {
|
|
133
|
+
var _a;
|
|
134
|
+
return (_a = plug.frontend_css) === null || _a === void 0 ? void 0 : _a.map(f => `<link rel='stylesheet' type='text/css' href='${pub + k + '/' + f}' plugin=${JSON.stringify(k)}/>`);
|
|
135
|
+
})
|
|
136
|
+
.flat().filter(Boolean).join('\n')}
|
|
137
|
+
${!isFrontend ? '' : (0, plugins_1.mapPlugins)((plug, k) => {
|
|
138
|
+
var _a;
|
|
139
|
+
return (_a = plug.frontend_js) === null || _a === void 0 ? void 0 : _a.map(f => `<script defer plugin=${JSON.stringify(k)} src='${pub + k + '/' + f}'></script>`);
|
|
140
|
+
})
|
|
141
|
+
.flat().filter(Boolean).join('\n')}
|
|
142
|
+
`);
|
|
130
143
|
if (isFrontend)
|
|
131
144
|
ret = ret
|
|
132
145
|
.replace('<body>', '<body>' + (0, customHtml_1.getSection)('top'))
|
package/src/update.js
CHANGED
|
@@ -10,9 +10,8 @@ const misc_1 = require("./misc");
|
|
|
10
10
|
const fs_1 = require("fs");
|
|
11
11
|
const plugins_1 = require("./plugins");
|
|
12
12
|
const promises_1 = require("fs/promises");
|
|
13
|
-
const HFS_REPO = 'rejetto/hfs';
|
|
14
13
|
async function getUpdate() {
|
|
15
|
-
const [latest] = await (0, github_1.getRepoInfo)(HFS_REPO + '/releases?per_page=1');
|
|
14
|
+
const [latest] = await (0, github_1.getRepoInfo)(const_1.HFS_REPO + '/releases?per_page=1');
|
|
16
15
|
if (latest.name === const_1.VERSION)
|
|
17
16
|
throw "you already have the latest version: " + const_1.VERSION;
|
|
18
17
|
return latest;
|
package/src/upload.js
CHANGED
|
@@ -15,14 +15,13 @@ const util_os_1 = require("./util-os");
|
|
|
15
15
|
const connections_1 = require("./connections");
|
|
16
16
|
const throttler_1 = require("./throttler");
|
|
17
17
|
const lodash_1 = __importDefault(require("lodash"));
|
|
18
|
-
exports.deleteUnfinishedUploadsAfter = (0, config_1.defineConfig)('delete_unfinished_uploads_after');
|
|
18
|
+
exports.deleteUnfinishedUploadsAfter = (0, config_1.defineConfig)('delete_unfinished_uploads_after', 86400);
|
|
19
19
|
exports.minAvailableMb = (0, config_1.defineConfig)('min_available_mb', 100);
|
|
20
20
|
const dontOverwriteUploading = (0, config_1.defineConfig)('dont_overwrite_uploading', false);
|
|
21
21
|
const waitingToBeDeleted = {};
|
|
22
22
|
function uploadWriter(base, path, ctx) {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
return fail(res);
|
|
23
|
+
if ((0, vfs_1.statusCodeForMissingPerm)(base, 'can_upload', ctx))
|
|
24
|
+
return fail();
|
|
26
25
|
const fullPath = (0, path_1.join)(base.source, path);
|
|
27
26
|
const dir = (0, path_1.dirname)(fullPath);
|
|
28
27
|
const min = exports.minAvailableMb.get() * (1 << 20);
|
|
@@ -36,7 +35,7 @@ function uploadWriter(base, path, ctx) {
|
|
|
36
35
|
return fail(const_1.HTTP_PAYLOAD_TOO_LARGE);
|
|
37
36
|
}
|
|
38
37
|
catch (e) {
|
|
39
|
-
console.warn("can't check disk size", e.message || String(e));
|
|
38
|
+
console.warn("can't check disk size:", e.message || String(e));
|
|
40
39
|
}
|
|
41
40
|
fs_1.default.mkdirSync(dir, { recursive: true });
|
|
42
41
|
const keepName = (0, path_1.basename)(fullPath).slice(-200);
|
|
@@ -122,7 +121,8 @@ function uploadWriter(base, path, ctx) {
|
|
|
122
121
|
delete waitingToBeDeleted[path];
|
|
123
122
|
}
|
|
124
123
|
function fail(status) {
|
|
125
|
-
|
|
124
|
+
if (status)
|
|
125
|
+
ctx.status = status;
|
|
126
126
|
(0, frontEndApis_1.notifyClient)(ctx, 'upload.status', { [path]: ctx.status }); // allow browsers to detect failure while still sending body
|
|
127
127
|
}
|
|
128
128
|
}
|
package/src/util-http.js
CHANGED
|
@@ -20,13 +20,15 @@ function httpsString(url, options = {}) {
|
|
|
20
20
|
exports.httpsString = httpsString;
|
|
21
21
|
function httpsStream(url, options = {}) {
|
|
22
22
|
return new Promise((resolve, reject) => {
|
|
23
|
-
node_https_1.default.request(url, options, res => {
|
|
23
|
+
const req = node_https_1.default.request(url, options, res => {
|
|
24
24
|
if (!res.statusCode || res.statusCode >= 400)
|
|
25
|
-
|
|
25
|
+
return reject(new Error(String(res.statusCode), { cause: res }));
|
|
26
26
|
if (res.statusCode === const_1.HTTP_TEMPORARY_REDIRECT && res.headers.location)
|
|
27
27
|
return resolve(httpsStream(res.headers.location, options));
|
|
28
28
|
resolve(res);
|
|
29
|
-
}).on('error',
|
|
29
|
+
}).on('error', e => {
|
|
30
|
+
reject(req.res || e);
|
|
31
|
+
}).end();
|
|
30
32
|
});
|
|
31
33
|
}
|
|
32
34
|
exports.httpsStream = httpsStream;
|
package/src/vfs.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.walkNode = exports.statusCodeForMissingPerm = exports.hasPermission = exports.nodeIsDirectory = exports.getNodeName = exports.saveVfs = exports.vfs = exports.urlToNode = exports.isSameFilenameAs = exports.MIME_AUTO = exports.defaultPerms = void 0;
|
|
7
|
+
exports.parentMaskApplier = exports.masksCouldGivePermission = exports.walkNode = exports.statusCodeForMissingPerm = exports.hasPermission = exports.nodeIsDirectory = exports.getNodeName = exports.saveVfs = exports.vfs = exports.urlToNode = exports.applyParentToChild = exports.isSameFilenameAs = exports.MIME_AUTO = exports.PERM_KEYS = exports.defaultPerms = exports.WHO_ANY_ACCOUNT = exports.WHO_NO_ONE = exports.WHO_ANYONE = void 0;
|
|
8
8
|
const promises_1 = __importDefault(require("fs/promises"));
|
|
9
9
|
const path_1 = require("path");
|
|
10
10
|
const misc_1 = require("./misc");
|
|
@@ -13,21 +13,25 @@ const config_1 = require("./config");
|
|
|
13
13
|
const const_1 = require("./const");
|
|
14
14
|
const events_1 = __importDefault(require("./events"));
|
|
15
15
|
const perm_1 = require("./perm");
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
exports.WHO_ANYONE = true;
|
|
17
|
+
exports.WHO_NO_ONE = false;
|
|
18
|
+
exports.WHO_ANY_ACCOUNT = '*';
|
|
19
19
|
exports.defaultPerms = {
|
|
20
|
-
can_see:
|
|
21
|
-
can_read: WHO_ANYONE,
|
|
22
|
-
can_list:
|
|
23
|
-
can_upload: WHO_NO_ONE,
|
|
24
|
-
can_delete: WHO_NO_ONE,
|
|
20
|
+
can_see: 'can_read',
|
|
21
|
+
can_read: exports.WHO_ANYONE,
|
|
22
|
+
can_list: 'can_read',
|
|
23
|
+
can_upload: exports.WHO_NO_ONE,
|
|
24
|
+
can_delete: exports.WHO_NO_ONE,
|
|
25
25
|
};
|
|
26
|
+
exports.PERM_KEYS = (0, misc_1.typedKeys)(exports.defaultPerms);
|
|
26
27
|
exports.MIME_AUTO = 'auto';
|
|
27
28
|
function inheritFromParent(parent, child) {
|
|
28
29
|
var _a, _b, _c;
|
|
29
|
-
for (const k of (0, misc_1.typedKeys)(exports.defaultPerms))
|
|
30
|
-
|
|
30
|
+
for (const k of (0, misc_1.typedKeys)(exports.defaultPerms)) {
|
|
31
|
+
const v = parent[k];
|
|
32
|
+
if (v !== undefined) // small optimization: don't expand the object
|
|
33
|
+
(_a = child[k]) !== null && _a !== void 0 ? _a : (child[k] = v);
|
|
34
|
+
}
|
|
31
35
|
if (typeof parent.mime === 'object' && typeof child.mime === 'object')
|
|
32
36
|
lodash_1.default.defaults(child.mime, parent.mime);
|
|
33
37
|
else
|
|
@@ -40,6 +44,20 @@ function isSameFilenameAs(name) {
|
|
|
40
44
|
return (other) => lc === (typeof other === 'string' ? other : getNodeName(other)).toLowerCase();
|
|
41
45
|
}
|
|
42
46
|
exports.isSameFilenameAs = isSameFilenameAs;
|
|
47
|
+
function applyParentToChild(child, parent, name) {
|
|
48
|
+
const ret = {
|
|
49
|
+
...child,
|
|
50
|
+
original: child,
|
|
51
|
+
isTemp: true,
|
|
52
|
+
parent,
|
|
53
|
+
};
|
|
54
|
+
name || (name = child ? getNodeName(child) : '');
|
|
55
|
+
inheritMasks(ret, parent, name);
|
|
56
|
+
parentMaskApplier(parent)(ret, name);
|
|
57
|
+
inheritFromParent(parent, ret);
|
|
58
|
+
return ret;
|
|
59
|
+
}
|
|
60
|
+
exports.applyParentToChild = applyParentToChild;
|
|
43
61
|
async function urlToNode(url, ctx, parent = exports.vfs, getRest) {
|
|
44
62
|
var _a;
|
|
45
63
|
let initialSlashes = 0;
|
|
@@ -57,19 +75,11 @@ async function urlToNode(url, ctx, parent = exports.vfs, getRest) {
|
|
|
57
75
|
}
|
|
58
76
|
// does the tree node have a child that goes by this name?
|
|
59
77
|
const child = (_a = parent.children) === null || _a === void 0 ? void 0 : _a.find(isSameFilenameAs(name));
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
isTemp: true,
|
|
64
|
-
};
|
|
65
|
-
inheritMasks(ret, parent, name);
|
|
66
|
-
applyMasks(ret, parent, name);
|
|
67
|
-
inheritFromParent(parent, ret);
|
|
78
|
+
if (!child && !parent.source)
|
|
79
|
+
return; // on tree or on disk
|
|
80
|
+
const ret = applyParentToChild(child, parent, name);
|
|
68
81
|
if (child) // yes
|
|
69
82
|
return urlToNode(rest, ctx, ret, getRest);
|
|
70
|
-
// not in the tree, we can see consider continuing on the disk
|
|
71
|
-
if (!parent.source)
|
|
72
|
-
return; // but then we need the current node to be linked to the disk, otherwise, we give up
|
|
73
83
|
let onDisk = name;
|
|
74
84
|
if (parent.rename) { // reverse the mapping
|
|
75
85
|
for (const [from, to] of Object.entries(parent.rename))
|
|
@@ -124,27 +134,47 @@ async function nodeIsDirectory(node) {
|
|
|
124
134
|
}
|
|
125
135
|
exports.nodeIsDirectory = nodeIsDirectory;
|
|
126
136
|
function hasPermission(node, perm, ctx) {
|
|
127
|
-
|
|
128
|
-
return (node.source || perm !== 'can_upload') // Upload possible only if we know where to store. First check node.source because is supposedly faster.
|
|
129
|
-
&& matchWho((_a = node[perm]) !== null && _a !== void 0 ? _a : exports.defaultPerms[perm], ctx);
|
|
137
|
+
return !statusCodeForMissingPerm(node, perm, ctx, false);
|
|
130
138
|
}
|
|
131
139
|
exports.hasPermission = hasPermission;
|
|
132
|
-
function statusCodeForMissingPerm(node, perm, ctx) {
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
140
|
+
function statusCodeForMissingPerm(node, perm, ctx, assign = true) {
|
|
141
|
+
const ret = getCode();
|
|
142
|
+
if (ret && assign)
|
|
143
|
+
ctx.status = ret;
|
|
144
|
+
return ret;
|
|
145
|
+
function getCode() {
|
|
146
|
+
var _a;
|
|
147
|
+
if (!node.source && perm === 'can_upload') // Upload possible only if we know where to store. First check node.source because is supposedly faster.
|
|
148
|
+
return const_1.HTTP_FORBIDDEN;
|
|
149
|
+
// calculate value of permission resolving references to other permissions, avoiding infinite loop
|
|
150
|
+
let who;
|
|
151
|
+
let max = exports.PERM_KEYS.length;
|
|
152
|
+
do {
|
|
153
|
+
who = (_a = node[perm]) !== null && _a !== void 0 ? _a : exports.defaultPerms[perm];
|
|
154
|
+
if (!max-- || typeof who !== 'string' || who === exports.WHO_ANY_ACCOUNT)
|
|
155
|
+
break;
|
|
156
|
+
perm = who;
|
|
157
|
+
} while (1);
|
|
158
|
+
if (Array.isArray(who)) {
|
|
159
|
+
const arr = who; // shut up ts
|
|
160
|
+
// check if I or any ancestor match `who`, but cache ancestors' usernames inside context state
|
|
161
|
+
const some = (0, misc_1.getOrSet)(ctx.state, 'usernames', () => (0, perm_1.getCurrentUsernameExpanded)(ctx))
|
|
162
|
+
.some((u) => arr.includes(u));
|
|
163
|
+
return some ? 0 : const_1.HTTP_UNAUTHORIZED;
|
|
164
|
+
}
|
|
165
|
+
return typeof who === 'boolean' ? (who ? 0 : const_1.HTTP_FORBIDDEN)
|
|
166
|
+
: who === exports.WHO_ANY_ACCOUNT ? (ctx.state.account ? 0 : const_1.HTTP_UNAUTHORIZED)
|
|
167
|
+
: (() => { throw Error('invalid permission: ' + who); })();
|
|
168
|
+
}
|
|
136
169
|
}
|
|
137
170
|
exports.statusCodeForMissingPerm = statusCodeForMissingPerm;
|
|
138
171
|
// it's responsibility of the caller to verify you have list permission on parent, as callers have different needs.
|
|
139
172
|
// Too many parameters: consider object, but benchmark against degraded recursion on huge folders.
|
|
140
173
|
async function* walkNode(parent, ctx, depth = 0, prefixPath = '', requiredPerm) {
|
|
141
174
|
var _a;
|
|
142
|
-
if (requiredPerm && ctx
|
|
143
|
-
&& !hasPermission(parent, requiredPerm, ctx)
|
|
144
|
-
&& !masksCouldGivePermission(parent.masks))
|
|
145
|
-
return; // no permission, no reason to continue
|
|
146
175
|
const { children, source } = parent;
|
|
147
176
|
const took = prefixPath ? undefined : new Set();
|
|
177
|
+
const maskApplier = parentMaskApplier(parent);
|
|
148
178
|
if (children)
|
|
149
179
|
for (const child of children) {
|
|
150
180
|
const nodeName = getNodeName(child);
|
|
@@ -162,6 +192,10 @@ async function* walkNode(parent, ctx, depth = 0, prefixPath = '', requiredPerm)
|
|
|
162
192
|
}
|
|
163
193
|
if (!source)
|
|
164
194
|
return;
|
|
195
|
+
if (requiredPerm && ctx // no permission, no reason to continue (at least for dynamic elements)
|
|
196
|
+
&& !hasPermission(parent, requiredPerm, ctx)
|
|
197
|
+
&& !masksCouldGivePermission(parent.masks, requiredPerm))
|
|
198
|
+
return;
|
|
165
199
|
try {
|
|
166
200
|
let lastDir = prefixPath.slice(0, -1) || '.';
|
|
167
201
|
const map = new Map();
|
|
@@ -196,46 +230,58 @@ async function* walkNode(parent, ctx, depth = 0, prefixPath = '', requiredPerm)
|
|
|
196
230
|
// item will be changed, so be sure to pass a temp node
|
|
197
231
|
function canSee(item) {
|
|
198
232
|
// we basename for depth>0 where we already have the rest of the path in the parent's url, and would be duplicated
|
|
199
|
-
|
|
233
|
+
maskApplier(item, (0, path_1.basename)(getNodeName(item)));
|
|
200
234
|
inheritFromParent(parent, item);
|
|
201
235
|
if (ctx && !hasPermission(item, 'can_see', ctx))
|
|
202
236
|
return;
|
|
203
237
|
item.isTemp = true;
|
|
204
238
|
return item;
|
|
205
239
|
}
|
|
206
|
-
function masksCouldGivePermission(masks) {
|
|
207
|
-
if (!masks)
|
|
208
|
-
return false;
|
|
209
|
-
for (const [, props] of Object.entries(masks)) {
|
|
210
|
-
const v = props[requiredPerm];
|
|
211
|
-
if (v && (!ctx || matchWho(v, ctx))) // without ctx we can't say, so it could
|
|
212
|
-
return true;
|
|
213
|
-
if (masksCouldGivePermission(props.masks))
|
|
214
|
-
return true;
|
|
215
|
-
}
|
|
216
|
-
return false;
|
|
217
|
-
}
|
|
218
240
|
}
|
|
219
241
|
exports.walkNode = walkNode;
|
|
220
|
-
function
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
242
|
+
function masksCouldGivePermission(masks, perm) {
|
|
243
|
+
return masks !== undefined && Object.values(masks).some(props => props[perm] || masksCouldGivePermission(props.masks, perm));
|
|
244
|
+
}
|
|
245
|
+
exports.masksCouldGivePermission = masksCouldGivePermission;
|
|
246
|
+
function parentMaskApplier(parent) {
|
|
247
|
+
const matchers = Object.entries(parent.masks || {}).map(([k, v]) => {
|
|
248
|
+
k = k.startsWith('**/') ? k.slice(3) : !k.includes('/') ? k : '';
|
|
249
|
+
if (!k)
|
|
250
|
+
return;
|
|
251
|
+
const m = (0, misc_1.makeMatcher)(k);
|
|
252
|
+
return [m, v];
|
|
253
|
+
});
|
|
254
|
+
return (item, virtualBasename) => {
|
|
255
|
+
if (virtualBasename === undefined)
|
|
256
|
+
virtualBasename = getNodeName(item);
|
|
257
|
+
for (const entry of matchers) {
|
|
258
|
+
if (!entry)
|
|
259
|
+
continue;
|
|
260
|
+
const [matcher, mods] = entry;
|
|
261
|
+
if (!matcher(virtualBasename))
|
|
262
|
+
continue;
|
|
263
|
+
if (item.masks)
|
|
264
|
+
item.masks = lodash_1.default.merge(lodash_1.default.cloneDeep(mods.masks), item.masks); // item.masks must take precedence
|
|
265
|
+
lodash_1.default.defaults(item, mods);
|
|
266
|
+
}
|
|
267
|
+
};
|
|
228
268
|
}
|
|
269
|
+
exports.parentMaskApplier = parentMaskApplier;
|
|
229
270
|
function inheritMasks(item, parent, virtualBasename) {
|
|
230
271
|
const { masks } = parent;
|
|
231
272
|
if (!masks)
|
|
232
273
|
return;
|
|
233
274
|
const o = {};
|
|
234
|
-
for (const [k, v] of Object.entries(masks))
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
o[k
|
|
275
|
+
for (const [k, v] of Object.entries(masks)) {
|
|
276
|
+
const neg = k[0] === '!' && k[1] !== '(' ? '!' : '';
|
|
277
|
+
const withoutNeg = neg ? k.slice(1) : k;
|
|
278
|
+
if (withoutNeg.startsWith('**'))
|
|
279
|
+
o[k] = v;
|
|
280
|
+
else if (withoutNeg.startsWith('*/'))
|
|
281
|
+
o[neg + withoutNeg.slice(2)] = v;
|
|
282
|
+
else if (withoutNeg.startsWith(virtualBasename + '/'))
|
|
283
|
+
o[neg + withoutNeg.slice(virtualBasename.length + 1)] = v;
|
|
284
|
+
}
|
|
239
285
|
if (Object.keys(o).length)
|
|
240
286
|
item.masks = lodash_1.default.defaults(item.masks, o);
|
|
241
287
|
}
|
|
@@ -247,24 +293,18 @@ function renameUnderPath(rename, path) {
|
|
|
247
293
|
delete rename[''];
|
|
248
294
|
return lodash_1.default.isEmpty(rename) ? undefined : rename;
|
|
249
295
|
}
|
|
250
|
-
function matchWho(who, ctx) {
|
|
251
|
-
return who === WHO_ANYONE
|
|
252
|
-
|| who === WHO_ANY_ACCOUNT && Boolean(ctx.state.account)
|
|
253
|
-
|| Array.isArray(who) // check if I or any ancestor match `who`, but cache ancestors' usernames inside context state
|
|
254
|
-
&& (0, misc_1.getOrSet)(ctx.state, 'usernames', () => (0, perm_1.getCurrentUsernameExpanded)(ctx)).some((u) => who.includes(u));
|
|
255
|
-
}
|
|
256
296
|
events_1.default.on('accountRenamed', (from, to) => {
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
function recur(n) {
|
|
297
|
+
;
|
|
298
|
+
(function renameInNode(n) {
|
|
260
299
|
var _a;
|
|
261
|
-
for (const k of
|
|
262
|
-
|
|
300
|
+
for (const k of exports.PERM_KEYS)
|
|
301
|
+
renameInPerm(n[k]);
|
|
263
302
|
if (n.masks)
|
|
264
|
-
Object.values(n.masks).forEach(
|
|
265
|
-
(_a = n.children) === null || _a === void 0 ? void 0 : _a.forEach(
|
|
266
|
-
}
|
|
267
|
-
|
|
303
|
+
Object.values(n.masks).forEach(renameInNode);
|
|
304
|
+
(_a = n.children) === null || _a === void 0 ? void 0 : _a.forEach(renameInNode);
|
|
305
|
+
})(exports.vfs);
|
|
306
|
+
saveVfs();
|
|
307
|
+
function renameInPerm(a) {
|
|
268
308
|
if (!Array.isArray(a))
|
|
269
309
|
return;
|
|
270
310
|
for (let i = 0; i < a.length; i++)
|
package/src/zip.js
CHANGED
|
@@ -26,16 +26,16 @@ async function zipStreamFromFolder(node, ctx) {
|
|
|
26
26
|
const filter = (0, misc_1.pattern2filter)(String(ctx.query.search || ''));
|
|
27
27
|
const walker = !list ? (0, vfs_1.walkNode)(node, ctx, Infinity, '', 'can_read')
|
|
28
28
|
: (async function* () {
|
|
29
|
-
for await (const
|
|
30
|
-
const subNode = await (0, vfs_1.urlToNode)(
|
|
29
|
+
for await (const uri of list) {
|
|
30
|
+
const subNode = await (0, vfs_1.urlToNode)(uri, ctx, node);
|
|
31
31
|
if (!subNode)
|
|
32
32
|
continue;
|
|
33
33
|
if (await (0, vfs_1.nodeIsDirectory)(subNode)) { // a directory needs to walked
|
|
34
34
|
if ((0, vfs_1.hasPermission)(subNode, 'can_list', ctx))
|
|
35
|
-
yield* (0, vfs_1.walkNode)(subNode, ctx, Infinity,
|
|
35
|
+
yield* (0, vfs_1.walkNode)(subNode, ctx, Infinity, uri + '/', 'can_read');
|
|
36
36
|
continue;
|
|
37
37
|
}
|
|
38
|
-
let folder = (0, path_1.dirname)(
|
|
38
|
+
let folder = (0, path_1.dirname)(decodeURIComponent(uri)); // decodeURI() won't account for %23=#
|
|
39
39
|
folder = folder === '.' ? '' : folder + '/';
|
|
40
40
|
yield { ...subNode, name: folder + (0, vfs_1.getNodeName)(subNode) }; // reflect relative path in archive, otherwise way may have name-clashes
|
|
41
41
|
}
|