hfs 0.40.1 → 0.41.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 +38 -99
- package/admin/assets/index-5c926903.js +510 -0
- package/admin/assets/{index-fe2c1463.css → index-ba8da62c.css} +1 -1
- package/{frontend/assets/sha512-962ae274.js → admin/assets/sha512-de1e5bd3.js} +1 -1
- package/admin/index.html +2 -2
- package/frontend/assets/{index-ffbcd4c9.css → index-25b49014.css} +1 -1
- package/frontend/assets/index-7ceb2f4c.js +94 -0
- package/{admin/assets/sha512-3a11d514.js → frontend/assets/sha512-09fdf0a5.js} +1 -1
- package/frontend/index.html +2 -2
- package/package.json +2 -1
- package/src/api.file_list.js +4 -4
- package/src/api.lang.js +1 -1
- package/src/api.vfs.js +4 -4
- package/src/const.js +1 -1
- package/src/frontEndApis.js +13 -11
- package/src/middlewares.js +12 -20
- package/src/misc.js +7 -3
- package/src/serveFile.js +1 -1
- package/src/serveGuiFiles.js +1 -1
- package/src/upload.js +3 -2
- package/src/util-files.js +6 -4
- package/src/vfs.js +61 -26
- package/src/zip.js +7 -3
- package/admin/assets/index-87c2ebd1.js +0 -510
- package/frontend/assets/index-9f5a7cc5.js +0 -94
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{c as SF}from"./index-
|
|
1
|
+
import{c as SF}from"./index-7ceb2f4c.js";function OF(iF,hF){for(var eF=0;eF<hF.length;eF++){const tF=hF[eF];if(typeof tF!="string"&&!Array.isArray(tF)){for(const w in tF)if(w!=="default"&&!(w in iF)){const lF=Object.getOwnPropertyDescriptor(tF,w);lF&&Object.defineProperty(iF,w,lF.get?lF:{enumerable:!0,get:()=>tF[w]})}}}return Object.freeze(Object.defineProperty(iF,Symbol.toStringTag,{value:"Module"}))}var EF={},UF={get exports(){return EF},set exports(iF){EF=iF}};/*
|
|
2
2
|
* [js-sha512]{@link https://github.com/emn178/js-sha512}
|
|
3
3
|
*
|
|
4
4
|
* @version 0.8.0
|
package/frontend/index.html
CHANGED
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=0" />
|
|
6
6
|
<link href="/fontello.css" rel="stylesheet" />
|
|
7
7
|
|
|
8
|
-
<script type="module" crossorigin src="/assets/index-
|
|
9
|
-
<link rel="stylesheet" href="/assets/index-
|
|
8
|
+
<script type="module" crossorigin src="/assets/index-7ceb2f4c.js"></script>
|
|
9
|
+
<link rel="stylesheet" href="/assets/index-25b49014.css">
|
|
10
10
|
</head>
|
|
11
11
|
<body>
|
|
12
12
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hfs",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.41.0",
|
|
4
4
|
"description": "HTTP File Server",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"file server",
|
|
@@ -77,6 +77,7 @@
|
|
|
77
77
|
"open": "^8.4.0",
|
|
78
78
|
"tssrp6a": "^3.0.0",
|
|
79
79
|
"unzip-stream": "^0.3.1",
|
|
80
|
+
"valtio": "^1.10.3",
|
|
80
81
|
"yaml": "^2.0.0-10"
|
|
81
82
|
},
|
|
82
83
|
"devDependencies": {
|
package/src/api.file_list.js
CHANGED
|
@@ -17,8 +17,9 @@ const file_list = async ({ path, offset, limit, search, omit, sse }, ctx) => {
|
|
|
17
17
|
const list = new apiMiddleware_1.SendListReadable();
|
|
18
18
|
if (!node)
|
|
19
19
|
return fail(const_1.HTTP_NOT_FOUND);
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
const res = (0, vfs_1.statusCodeForMissingPerm)(node, 'can_list', ctx);
|
|
21
|
+
if (res)
|
|
22
|
+
return fail(res);
|
|
22
23
|
if ((0, misc_1.dirTraversal)(search))
|
|
23
24
|
return fail(const_1.HTTP_FOOL);
|
|
24
25
|
if (node.default)
|
|
@@ -50,8 +51,7 @@ const file_list = async ({ path, offset, limit, search, omit, sse }, ctx) => {
|
|
|
50
51
|
function fail(code) {
|
|
51
52
|
if (!sse)
|
|
52
53
|
return new apiMiddleware_1.ApiError(code);
|
|
53
|
-
list.error(code);
|
|
54
|
-
list.close();
|
|
54
|
+
list.error(code, true);
|
|
55
55
|
return list;
|
|
56
56
|
}
|
|
57
57
|
async function* produceEntries() {
|
package/src/api.lang.js
CHANGED
|
@@ -13,7 +13,7 @@ const misc_1 = require("./misc");
|
|
|
13
13
|
const PREFIX = 'hfs-lang-';
|
|
14
14
|
const SUFFIX = '.json';
|
|
15
15
|
const apis = {
|
|
16
|
-
|
|
16
|
+
list_langs() {
|
|
17
17
|
return new apiMiddleware_1.SendListReadable({
|
|
18
18
|
doAtStart: async (list) => {
|
|
19
19
|
for await (let name of fast_glob_1.default.stream(code2file('*'))) {
|
package/src/api.vfs.js
CHANGED
|
@@ -151,21 +151,21 @@ const apis = {
|
|
|
151
151
|
}
|
|
152
152
|
try {
|
|
153
153
|
path = (0, misc_1.isWindowsDrive)(path) ? path + '\\' : (0, path_1.resolve)(path || '/');
|
|
154
|
-
for await (const name of (0, misc_1.dirStream)(path)) {
|
|
154
|
+
for await (const [name, isDir] of (0, misc_1.dirStream)(path)) {
|
|
155
155
|
if (ctx.req.aborted)
|
|
156
156
|
return;
|
|
157
157
|
try {
|
|
158
|
-
|
|
159
|
-
if (stats.isFile())
|
|
158
|
+
if (!isDir)
|
|
160
159
|
if (!files || fileMask && !(0, micromatch_1.isMatch)(name, fileMask))
|
|
161
160
|
continue;
|
|
161
|
+
const stats = await (0, promises_1.stat)((0, path_1.join)(path, name));
|
|
162
162
|
yield {
|
|
163
163
|
add: {
|
|
164
164
|
n: name,
|
|
165
165
|
s: stats.size,
|
|
166
166
|
c: stats.ctime,
|
|
167
167
|
m: stats.mtime,
|
|
168
|
-
k:
|
|
168
|
+
k: isDir ? 'd' : undefined,
|
|
169
169
|
}
|
|
170
170
|
};
|
|
171
171
|
}
|
package/src/const.js
CHANGED
|
@@ -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-03-
|
|
41
|
+
exports.BUILD_TIMESTAMP = "2023-03-18T22:44:28.592Z";
|
|
42
42
|
const pkg = JSON.parse(fs.readFileSync(PKG_PATH, 'utf8'));
|
|
43
43
|
exports.VERSION = pkg.version;
|
|
44
44
|
exports.DAY = 86400000;
|
package/src/frontEndApis.js
CHANGED
|
@@ -89,23 +89,25 @@ exports.frontEndApis = {
|
|
|
89
89
|
},
|
|
90
90
|
async load_lang({ lang, embedded }) {
|
|
91
91
|
const ret = {};
|
|
92
|
-
const langs = (0, misc_1.wantArray)(lang);
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
92
|
+
const langs = (0, misc_1.wantArray)(lang).map(x => x.toLowerCase());
|
|
93
|
+
let i = 0;
|
|
94
|
+
while (i < langs.length) {
|
|
95
|
+
let x = langs[i];
|
|
96
|
+
if (x === embedded)
|
|
96
97
|
break;
|
|
97
98
|
try {
|
|
98
|
-
ret[
|
|
99
|
+
ret[x] = JSON.parse(await (0, promises_1.readFile)(`hfs-lang-${x}.json`, 'utf8'));
|
|
99
100
|
}
|
|
100
101
|
catch (_a) {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
102
|
+
do {
|
|
103
|
+
x = x.substring(0, x.lastIndexOf('-'));
|
|
104
|
+
} while (x && langs.includes(x));
|
|
105
|
+
if (x) {
|
|
106
|
+
langs[i] = x; // overwrite and retry
|
|
107
|
+
continue;
|
|
107
108
|
}
|
|
108
109
|
}
|
|
110
|
+
i++;
|
|
109
111
|
}
|
|
110
112
|
return ret;
|
|
111
113
|
}
|
package/src/middlewares.js
CHANGED
|
@@ -98,7 +98,9 @@ const serveGuiAndSharedFiles = async (ctx, next) => {
|
|
|
98
98
|
}
|
|
99
99
|
if (ctx.originalUrl === '/favicon.ico' && adminApis_1.favicon.get()) // originalUrl to not be subject to changes (vhosting plugin)
|
|
100
100
|
return (0, serveFile_1.serveFile)(ctx, adminApis_1.favicon.get());
|
|
101
|
-
|
|
101
|
+
let node = await (0, vfs_1.urlToNode)(path, ctx);
|
|
102
|
+
if ((node === null || node === void 0 ? void 0 : node.default) && (path.endsWith('/') || !node.default.match(/\.html?$/i))) // final/ needed on browser to make resource urls correctly
|
|
103
|
+
node = await (0, vfs_1.urlToNode)(node.default, ctx, node);
|
|
102
104
|
if (!node)
|
|
103
105
|
return ctx.status = const_1.HTTP_NOT_FOUND;
|
|
104
106
|
if (ctx.method === 'POST') { // curl -F upload=@file url/
|
|
@@ -112,15 +114,13 @@ const serveGuiAndSharedFiles = async (ctx, next) => {
|
|
|
112
114
|
await (0, stream_1.once)(form, 'end').catch(() => { });
|
|
113
115
|
return;
|
|
114
116
|
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
if (
|
|
120
|
-
return
|
|
121
|
-
|
|
122
|
-
if (!canRead) {
|
|
123
|
-
ctx.status = (0, vfs_1.cantReadStatusCode)(node);
|
|
117
|
+
if (!await (0, vfs_1.nodeIsDirectory)(node))
|
|
118
|
+
return !node.source && await next()
|
|
119
|
+
|| (0, vfs_1.statusCodeForMissingPerm)(node, 'can_read', ctx)
|
|
120
|
+
|| (0, serveFile_1.serveFileNode)(ctx, node);
|
|
121
|
+
if (!path.endsWith('/'))
|
|
122
|
+
return ctx.redirect(ctx.state.revProxyPath + ctx.originalUrl.replace(/(\?|$)/, '/$1')); // keep query-string, if any
|
|
123
|
+
if ((0, vfs_1.statusCodeForMissingPerm)(node, 'can_list', ctx)) {
|
|
124
124
|
if (ctx.status === const_1.HTTP_FORBIDDEN)
|
|
125
125
|
return;
|
|
126
126
|
const browserDetected = ctx.get('Upgrade-Insecure-Requests') || ctx.get('Sec-Fetch-Mode'); // ugh, heuristics
|
|
@@ -130,16 +130,8 @@ const serveGuiAndSharedFiles = async (ctx, next) => {
|
|
|
130
130
|
return serveFrontendFiles(ctx, next);
|
|
131
131
|
}
|
|
132
132
|
ctx.set({ server: 'HFS ' + const_1.BUILD_TIMESTAMP });
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
return await (0, zip_1.zipStreamFromFolder)(node, ctx);
|
|
136
|
-
if (node.default) {
|
|
137
|
-
const def = await (0, vfs_1.urlToNode)(path + node.default, ctx);
|
|
138
|
-
return !def ? next()
|
|
139
|
-
: (0, vfs_1.hasPermission)(def, 'can_read', ctx) ? (0, serveFile_1.serveFileNode)(ctx, def)
|
|
140
|
-
: ctx.status = (0, vfs_1.cantReadStatusCode)(def);
|
|
141
|
-
}
|
|
142
|
-
return serveFrontendFiles(ctx, next);
|
|
133
|
+
return ctx.query.get === 'zip' ? (0, zip_1.zipStreamFromFolder)(node, ctx)
|
|
134
|
+
: serveFrontendFiles(ctx, next);
|
|
143
135
|
};
|
|
144
136
|
exports.serveGuiAndSharedFiles = serveGuiAndSharedFiles;
|
|
145
137
|
let proxyDetected = false;
|
package/src/misc.js
CHANGED
|
@@ -47,17 +47,21 @@ function setHidden(dest, src) {
|
|
|
47
47
|
})));
|
|
48
48
|
}
|
|
49
49
|
exports.setHidden = setHidden;
|
|
50
|
-
function newObj(src,
|
|
50
|
+
function newObj(src, returnNewValue, recur = false) {
|
|
51
51
|
if (!src)
|
|
52
52
|
return {};
|
|
53
53
|
const pairs = Object.entries(src).map(([k, v]) => {
|
|
54
54
|
if (typeof k === 'symbol')
|
|
55
55
|
return;
|
|
56
56
|
let _k = k;
|
|
57
|
-
const
|
|
57
|
+
const curDepth = typeof recur === 'number' ? recur : 0;
|
|
58
|
+
let newV = returnNewValue(v, k, (newK) => {
|
|
58
59
|
_k = newK;
|
|
59
60
|
return true; // for convenient expression concatenation
|
|
60
|
-
});
|
|
61
|
+
}, curDepth);
|
|
62
|
+
if ((recur !== false || returnNewValue.length === 4) // if callback is using depth parameter, then it wants recursion
|
|
63
|
+
&& lodash_1.default.isPlainObject(newV)) // is it recurrable?
|
|
64
|
+
newV = newObj(newV, returnNewValue, curDepth + 1);
|
|
61
65
|
return _k !== undefined && [_k, newV];
|
|
62
66
|
});
|
|
63
67
|
return Object.fromEntries(onlyTruthy(pairs));
|
package/src/serveFile.js
CHANGED
|
@@ -41,7 +41,7 @@ async function serveFile(ctx, source, mime, content) {
|
|
|
41
41
|
if (!source)
|
|
42
42
|
return;
|
|
43
43
|
const fn = path_1.default.basename(source);
|
|
44
|
-
if (ctx.params
|
|
44
|
+
if ('dl' in ctx.params) // please, download
|
|
45
45
|
ctx.attachment(fn);
|
|
46
46
|
mime = mime !== null && mime !== void 0 ? mime : lodash_1.default.find(mimeCfg.get(), (v, k) => k > '' && (0, micromatch_1.isMatch)(fn, k)); // isMatch throws on an empty string
|
|
47
47
|
if (mime === vfs_1.MIME_AUTO)
|
package/src/serveGuiFiles.js
CHANGED
|
@@ -46,7 +46,7 @@ function serveStatic(uri) {
|
|
|
46
46
|
const folder = uri.slice(2, -1); // we know folder is very similar to uri
|
|
47
47
|
let cache = {};
|
|
48
48
|
(0, valtio_1.subscribe)(customHtml_1.customHtmlState, () => cache = {}); // reset cache at every change
|
|
49
|
-
return async (ctx
|
|
49
|
+
return async (ctx) => {
|
|
50
50
|
if (ctx.method === 'OPTIONS') {
|
|
51
51
|
ctx.status = const_1.HTTP_NO_CONTENT;
|
|
52
52
|
ctx.set({ Allow: 'OPTIONS, GET' });
|
package/src/upload.js
CHANGED
|
@@ -20,8 +20,9 @@ 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
|
-
|
|
23
|
+
const res = (0, vfs_1.statusCodeForMissingPerm)(base, 'can_upload', ctx);
|
|
24
|
+
if (res)
|
|
25
|
+
return fail(res);
|
|
25
26
|
const fullPath = (0, path_1.join)(base.source, path);
|
|
26
27
|
const dir = (0, path_1.dirname)(fullPath);
|
|
27
28
|
const min = exports.minAvailableMb.get() * (1 << 20);
|
package/src/util-files.js
CHANGED
|
@@ -106,18 +106,20 @@ async function* dirStream(path, deep) {
|
|
|
106
106
|
const skip = await getItemsToSkip(path);
|
|
107
107
|
for await (const entry of dirStream) {
|
|
108
108
|
let { path, dirent } = entry;
|
|
109
|
-
|
|
109
|
+
const isDir = dirent.isDirectory();
|
|
110
|
+
if (!isDir && !dirent.isFile())
|
|
110
111
|
continue;
|
|
111
112
|
path = String(path);
|
|
112
113
|
if (!(skip === null || skip === void 0 ? void 0 : skip.includes(path)))
|
|
113
|
-
yield path;
|
|
114
|
+
yield [path, isDir];
|
|
114
115
|
}
|
|
115
116
|
async function getItemsToSkip(path) {
|
|
116
117
|
if (!const_1.IS_WINDOWS)
|
|
117
118
|
return;
|
|
118
|
-
const
|
|
119
|
+
const winPath = path.replace(/\//g, '\\');
|
|
120
|
+
const out = await (0, util_os_1.runCmd)('dir', ['/ah', '/b', deep ? '/s' : '', winPath])
|
|
119
121
|
.catch(() => ''); // error in case of no matching file
|
|
120
|
-
return out.split('\r\n').slice(0, -1);
|
|
122
|
+
return out.split('\r\n').slice(0, -1).map(x => !deep ? x : x.slice(winPath.length + 1).replace(/\\/g, '/'));
|
|
121
123
|
}
|
|
122
124
|
}
|
|
123
125
|
exports.dirStream = dirStream;
|
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.
|
|
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;
|
|
8
8
|
const promises_1 = __importDefault(require("fs/promises"));
|
|
9
9
|
const path_1 = require("path");
|
|
10
10
|
const micromatch_1 = require("micromatch");
|
|
@@ -20,6 +20,7 @@ const WHO_ANY_ACCOUNT = '*';
|
|
|
20
20
|
exports.defaultPerms = {
|
|
21
21
|
can_see: WHO_ANYONE,
|
|
22
22
|
can_read: WHO_ANYONE,
|
|
23
|
+
can_list: WHO_ANYONE,
|
|
23
24
|
can_upload: WHO_NO_ONE,
|
|
24
25
|
can_delete: WHO_NO_ONE,
|
|
25
26
|
};
|
|
@@ -129,53 +130,91 @@ function hasPermission(node, perm, ctx) {
|
|
|
129
130
|
&& matchWho((_a = node[perm]) !== null && _a !== void 0 ? _a : exports.defaultPerms[perm], ctx);
|
|
130
131
|
}
|
|
131
132
|
exports.hasPermission = hasPermission;
|
|
132
|
-
|
|
133
|
+
function statusCodeForMissingPerm(node, perm, ctx) {
|
|
134
|
+
if (hasPermission(node, perm, ctx))
|
|
135
|
+
return false;
|
|
136
|
+
return ctx.status = node[perm] === false ? const_1.HTTP_FORBIDDEN : const_1.HTTP_UNAUTHORIZED;
|
|
137
|
+
}
|
|
138
|
+
exports.statusCodeForMissingPerm = statusCodeForMissingPerm;
|
|
139
|
+
// it's responsibility of the caller to verify you have list permission on parent, as callers have different needs.
|
|
140
|
+
// Too many parameters: consider object, but benchmark against degraded recursion on huge folders.
|
|
141
|
+
async function* walkNode(parent, ctx, depth = 0, prefixPath = '', requiredPerm) {
|
|
133
142
|
var _a;
|
|
143
|
+
if (requiredPerm && ctx
|
|
144
|
+
&& !hasPermission(parent, requiredPerm, ctx)
|
|
145
|
+
&& !masksCouldGivePermission(parent.masks))
|
|
146
|
+
return; // no permission, no reason to continue
|
|
134
147
|
const { children, source } = parent;
|
|
135
148
|
const took = prefixPath ? undefined : new Set();
|
|
136
149
|
if (children)
|
|
137
150
|
for (const child of children) {
|
|
138
|
-
const
|
|
151
|
+
const nodeName = getNodeName(child);
|
|
152
|
+
const name = prefixPath + nodeName;
|
|
139
153
|
took === null || took === void 0 ? void 0 : took.add(name);
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
154
|
+
const item = { ...child, name };
|
|
155
|
+
if (!canSee(item))
|
|
156
|
+
continue;
|
|
157
|
+
yield item;
|
|
158
|
+
if (!depth || !await nodeIsDirectory(child).catch(() => false))
|
|
159
|
+
continue;
|
|
160
|
+
inheritMasks(item, parent, nodeName);
|
|
161
|
+
if (!ctx || hasPermission(item, 'can_list', ctx)) // check perm before recursion
|
|
162
|
+
yield* walkNode(item, ctx, depth - 1, name + '/');
|
|
144
163
|
}
|
|
145
164
|
if (!source)
|
|
146
165
|
return;
|
|
147
166
|
try {
|
|
148
|
-
|
|
167
|
+
let lastDir = prefixPath.slice(0, -1) || '.';
|
|
168
|
+
const map = new Map();
|
|
169
|
+
map.set(lastDir, parent);
|
|
170
|
+
// it's important to keep using dirStream in deep-mode, as it is manyfold faster (it parallelizes)
|
|
171
|
+
for await (const [path, isDir] of (0, misc_1.dirStream)(source, depth)) {
|
|
149
172
|
if (ctx === null || ctx === void 0 ? void 0 : ctx.req.aborted)
|
|
150
173
|
return;
|
|
151
174
|
const name = prefixPath + (((_a = parent.rename) === null || _a === void 0 ? void 0 : _a[path]) || path);
|
|
152
175
|
if (took === null || took === void 0 ? void 0 : took.has(name))
|
|
153
176
|
continue;
|
|
154
|
-
|
|
177
|
+
if (depth) {
|
|
178
|
+
const dir = (0, path_1.dirname)(name);
|
|
179
|
+
if (dir !== lastDir)
|
|
180
|
+
parent = map.get(lastDir = dir);
|
|
181
|
+
}
|
|
182
|
+
const item = {
|
|
155
183
|
name,
|
|
156
184
|
source: (0, path_1.join)(source, path),
|
|
157
185
|
rename: renameUnderPath(parent.rename, path),
|
|
158
|
-
}
|
|
186
|
+
};
|
|
187
|
+
if (!canSee(item))
|
|
188
|
+
continue;
|
|
189
|
+
if (isDir)
|
|
190
|
+
map.set(name, item);
|
|
191
|
+
yield item;
|
|
159
192
|
}
|
|
160
193
|
}
|
|
161
194
|
catch (e) {
|
|
162
195
|
console.debug('glob', source, e); // ENOTDIR, or lacking permissions
|
|
163
196
|
}
|
|
164
197
|
// item will be changed, so be sure to pass a temp node
|
|
165
|
-
|
|
166
|
-
const name = getNodeName(item);
|
|
198
|
+
function canSee(item) {
|
|
167
199
|
// we basename for depth>0 where we already have the rest of the path in the parent's url, and would be duplicated
|
|
168
|
-
|
|
169
|
-
item.isTemp = true;
|
|
170
|
-
applyMasks(item, parent, virtualBasename);
|
|
200
|
+
applyMasks(item, parent, (0, path_1.basename)(getNodeName(item)));
|
|
171
201
|
inheritFromParent(parent, item);
|
|
172
202
|
if (ctx && !hasPermission(item, 'can_see', ctx))
|
|
173
203
|
return;
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
204
|
+
item.isTemp = true;
|
|
205
|
+
return item;
|
|
206
|
+
}
|
|
207
|
+
function masksCouldGivePermission(masks) {
|
|
208
|
+
if (!masks)
|
|
209
|
+
return false;
|
|
210
|
+
for (const [, props] of Object.entries(masks)) {
|
|
211
|
+
const v = props[requiredPerm];
|
|
212
|
+
if (v && (!ctx || matchWho(v, ctx))) // without ctx we can't say, so it could
|
|
213
|
+
return true;
|
|
214
|
+
if (masksCouldGivePermission(props.masks))
|
|
215
|
+
return true;
|
|
216
|
+
}
|
|
217
|
+
return false;
|
|
179
218
|
}
|
|
180
219
|
}
|
|
181
220
|
exports.walkNode = walkNode;
|
|
@@ -212,13 +251,9 @@ function renameUnderPath(rename, path) {
|
|
|
212
251
|
function matchWho(who, ctx) {
|
|
213
252
|
return who === WHO_ANYONE
|
|
214
253
|
|| who === WHO_ANY_ACCOUNT && Boolean(ctx.state.account)
|
|
215
|
-
|| Array.isArray(who)
|
|
216
|
-
|
|
217
|
-
}
|
|
218
|
-
function cantReadStatusCode(node) {
|
|
219
|
-
return node.can_read === false ? const_1.HTTP_FORBIDDEN : const_1.HTTP_UNAUTHORIZED;
|
|
254
|
+
|| Array.isArray(who) // check if I or any ancestor match `who`, but cache ancestors' usernames inside context state
|
|
255
|
+
&& (0, misc_1.getOrSet)(ctx.state, 'usernames', () => (0, perm_1.getCurrentUsernameExpanded)(ctx)).some((u) => who.includes(u));
|
|
220
256
|
}
|
|
221
|
-
exports.cantReadStatusCode = cantReadStatusCode;
|
|
222
257
|
events_1.default.on('accountRenamed', (from, to) => {
|
|
223
258
|
recur(exports.vfs);
|
|
224
259
|
saveVfs();
|
package/src/zip.js
CHANGED
|
@@ -14,6 +14,7 @@ const config_1 = require("./config");
|
|
|
14
14
|
const path_1 = require("path");
|
|
15
15
|
const serveFile_1 = require("./serveFile");
|
|
16
16
|
const const_1 = require("./const");
|
|
17
|
+
// expects 'node' to have had permissions checked by caller
|
|
17
18
|
async function zipStreamFromFolder(node, ctx) {
|
|
18
19
|
var _a;
|
|
19
20
|
ctx.status = const_1.HTTP_OK;
|
|
@@ -23,14 +24,15 @@ async function zipStreamFromFolder(node, ctx) {
|
|
|
23
24
|
const name = (list === null || list === void 0 ? void 0 : list.length) === 1 ? (0, path_1.basename)(list[0]) : (0, vfs_1.getNodeName)(node);
|
|
24
25
|
ctx.attachment(((0, misc_1.isWindowsDrive)(name) ? name[0] : (name || 'archive')) + '.zip');
|
|
25
26
|
const filter = (0, misc_1.pattern2filter)(String(ctx.query.search || ''));
|
|
26
|
-
const walker = !list ? (0, vfs_1.walkNode)(node, ctx, Infinity)
|
|
27
|
+
const walker = !list ? (0, vfs_1.walkNode)(node, ctx, Infinity, '', 'can_read')
|
|
27
28
|
: (async function* () {
|
|
28
29
|
for await (const el of list) {
|
|
29
30
|
const subNode = await (0, vfs_1.urlToNode)(el, ctx, node);
|
|
30
|
-
if (!subNode
|
|
31
|
+
if (!subNode)
|
|
31
32
|
continue;
|
|
32
33
|
if (await (0, vfs_1.nodeIsDirectory)(subNode)) { // a directory needs to walked
|
|
33
|
-
|
|
34
|
+
if ((0, vfs_1.hasPermission)(subNode, 'can_list', ctx))
|
|
35
|
+
yield* (0, vfs_1.walkNode)(subNode, ctx, Infinity, el + '/', 'can_read');
|
|
34
36
|
continue;
|
|
35
37
|
}
|
|
36
38
|
let folder = (0, path_1.dirname)(el);
|
|
@@ -39,6 +41,8 @@ async function zipStreamFromFolder(node, ctx) {
|
|
|
39
41
|
}
|
|
40
42
|
})();
|
|
41
43
|
const mappedWalker = (0, misc_1.filterMapGenerator)(walker, async (el) => {
|
|
44
|
+
if (!(0, vfs_1.hasPermission)(el, 'can_read', ctx))
|
|
45
|
+
return; // the fact you see it doesn't mean you can read it
|
|
42
46
|
const { source } = el;
|
|
43
47
|
const name = (0, vfs_1.getNodeName)(el);
|
|
44
48
|
if (!source || ctx.req.aborted || !filter(name))
|