hfs 0.45.0 → 0.46.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -16
- package/admin/assets/index-963c15a5.js +517 -0
- package/{frontend/assets/sha512-b32d8af7.js → admin/assets/sha512-471fbd54.js} +2 -2
- package/admin/index.html +1 -1
- package/frontend/assets/index-c0f02a72.js +94 -0
- package/frontend/assets/index-f2f8fd6b.css +1 -0
- package/{admin/assets/sha512-c0389c0d.js → frontend/assets/sha512-7dcc825b.js} +2 -2
- package/frontend/fontello.css +1 -1
- package/frontend/index.html +2 -3
- package/package.json +4 -3
- package/plugins/download-counter/plugin.js +3 -1
- package/plugins/download-counter/public/main.js +3 -2
- package/plugins/list-uploader/plugin.js +21 -0
- package/plugins/list-uploader/public/main.js +27 -0
- package/plugins/vhosting/plugin.js +6 -3
- package/src/adminApis.js +9 -3
- package/src/api.auth.js +4 -4
- package/src/api.file_list.js +4 -5
- package/src/api.monitor.js +7 -5
- package/src/api.plugins.js +57 -13
- package/src/api.vfs.js +2 -1
- package/src/apiMiddleware.js +4 -0
- package/src/commands.js +11 -0
- package/src/config.js +9 -5
- package/src/connections.js +1 -1
- package/src/const.js +4 -3
- package/src/frontEndApis.js +33 -1
- package/src/github.js +65 -33
- package/src/index.js +4 -4
- package/src/lang.js +3 -2
- package/src/langs/embedded.js +2 -1
- package/src/langs/hfs-lang-fr.json +16 -4
- package/src/langs/hfs-lang-it.json +11 -4
- package/src/langs/hfs-lang-ru.json +16 -4
- package/src/langs/hfs-lang-vi.json +116 -0
- package/src/log.js +50 -47
- package/src/middlewares.js +10 -3
- package/src/plugins.js +181 -98
- package/src/serveFile.js +13 -5
- package/src/serveGuiFiles.js +8 -10
- package/src/throttler.js +11 -6
- package/src/update.js +1 -4
- package/src/upload.js +26 -9
- package/src/util-files.js +25 -6
- package/src/vfs.js +6 -12
- package/src/zip.js +2 -2
- package/admin/assets/index-101f19c9.js +0 -510
- package/frontend/assets/index-59572489.css +0 -1
- package/frontend/assets/index-5aeff3af.js +0 -94
package/src/serveGuiFiles.js
CHANGED
|
@@ -42,6 +42,7 @@ const customHtml_1 = require("./customHtml");
|
|
|
42
42
|
const lodash_1 = __importDefault(require("lodash"));
|
|
43
43
|
const config_1 = require("./config");
|
|
44
44
|
const lang_1 = require("./lang");
|
|
45
|
+
const logGui = (0, config_1.defineConfig)('log_gui', false);
|
|
45
46
|
// in case of dev env we have our static files within the 'dist' folder'
|
|
46
47
|
const DEV_STATIC = process.env.DEV ? 'dist/' : '';
|
|
47
48
|
function serveStatic(uri) {
|
|
@@ -49,6 +50,8 @@ function serveStatic(uri) {
|
|
|
49
50
|
let cache = {};
|
|
50
51
|
(0, vanilla_1.subscribe)(customHtml_1.customHtmlState, () => cache = {}); // reset cache at every change
|
|
51
52
|
return async (ctx) => {
|
|
53
|
+
if (!logGui.get())
|
|
54
|
+
ctx.state.dont_log = true;
|
|
52
55
|
if (ctx.method === 'OPTIONS') {
|
|
53
56
|
ctx.status = const_1.HTTP_NO_CONTENT;
|
|
54
57
|
ctx.set({ Allow: 'OPTIONS, GET' });
|
|
@@ -104,7 +107,7 @@ async function treatIndex(ctx, filesUri, body) {
|
|
|
104
107
|
.replace('<body>', () => `<body>
|
|
105
108
|
${!isFrontend ? '' : `
|
|
106
109
|
<title>${adminApis_1.title.get()}</title>
|
|
107
|
-
<link rel="icon" href="${adminApis_1.favicon.get() ? '/favicon.ico' : '
|
|
110
|
+
<link rel="shortcut icon" href="${adminApis_1.favicon.get() ? '/favicon.ico' : '#'}" />
|
|
108
111
|
`}
|
|
109
112
|
<script>
|
|
110
113
|
HFS = ${JSON.stringify({
|
|
@@ -120,13 +123,6 @@ async function treatIndex(ctx, filesUri, body) {
|
|
|
120
123
|
}, null, 4)
|
|
121
124
|
.replace(/<(\/script)/g, '<"+"$1') /*avoid breaking our script container*/}
|
|
122
125
|
document.documentElement.setAttribute('ver', '${const_1.VERSION.split('-')[0] /*for style selectors*/}')
|
|
123
|
-
function getScriptAttr(k) {
|
|
124
|
-
return document.currentScript?.getAttribute(k)
|
|
125
|
-
|| console.error("this function must be called at the very top of your file")
|
|
126
|
-
}
|
|
127
|
-
HFS.getPluginKey = () => getScriptAttr('plugin')
|
|
128
|
-
HFS.getPluginConfig = () => HFS.plugins[HFS.getPluginKey()]
|
|
129
|
-
HFS.getPluginPublic = () => getScriptAttr('src')?.match(/^.*\\//)[0]
|
|
130
126
|
</script>
|
|
131
127
|
<style>
|
|
132
128
|
:root {
|
|
@@ -167,8 +163,10 @@ function serveProxied(port, uri) {
|
|
|
167
163
|
: adjustBundlerLinks(ctx, uri, data);
|
|
168
164
|
}
|
|
169
165
|
}));
|
|
170
|
-
return function () {
|
|
171
|
-
|
|
166
|
+
return function (ctx, next) {
|
|
167
|
+
if (!logGui.get())
|
|
168
|
+
ctx.state.dont_log = true;
|
|
169
|
+
return proxy(ctx, next);
|
|
172
170
|
};
|
|
173
171
|
}
|
|
174
172
|
function serveGuiFiles(proxyPort, uri) {
|
package/src/throttler.js
CHANGED
|
@@ -35,18 +35,23 @@ const throttler = async (ctx, next) => {
|
|
|
35
35
|
if (!conn)
|
|
36
36
|
throw 'assert throttler connection';
|
|
37
37
|
const ts = conn[SymThrStr] = new ThrottledStream_1.ThrottledStream(ipGroup.group, conn[SymThrStr]);
|
|
38
|
+
const offset = ts.getBytesSent();
|
|
38
39
|
let closed = false;
|
|
39
40
|
const DELAY = 1000;
|
|
40
41
|
const update = lodash_1.default.debounce(() => {
|
|
41
42
|
const ts = conn[SymThrStr];
|
|
42
43
|
const outSpeed = roundSpeed(ts.getSpeed());
|
|
43
|
-
(0, connections_1.updateConnection)(conn, {
|
|
44
|
+
(0, connections_1.updateConnection)(conn, {
|
|
45
|
+
outSpeed,
|
|
46
|
+
sent: conn.socket.bytesWritten,
|
|
47
|
+
opProgress: (conn.opOffset || 0) + ts.getBytesSent() / conn.opTotal,
|
|
48
|
+
});
|
|
44
49
|
/* in case this stream stands still for a while (before the end), we'll have neither 'sent' or 'close' events,
|
|
45
50
|
* so who will take care to updateConnection? This artificial next-call will ensure just that */
|
|
46
51
|
clearTimeout(conn[SymTimeout]);
|
|
47
52
|
if (outSpeed || !closed)
|
|
48
53
|
conn[SymTimeout] = setTimeout(update, DELAY);
|
|
49
|
-
}, DELAY, { maxWait: DELAY });
|
|
54
|
+
}, DELAY, { leading: true, maxWait: DELAY });
|
|
50
55
|
ts.on('sent', (n) => {
|
|
51
56
|
exports.totalSent += n;
|
|
52
57
|
update();
|
|
@@ -61,12 +66,12 @@ const throttler = async (ctx, next) => {
|
|
|
61
66
|
(_a = ipGroup.destroy) === null || _a === void 0 ? void 0 : _a.call(ipGroup);
|
|
62
67
|
delete ip2group[ctx.ip];
|
|
63
68
|
});
|
|
64
|
-
const
|
|
69
|
+
const downloadTotal = ctx.response.length;
|
|
65
70
|
ctx.body = ctx.body.pipe(ts);
|
|
66
|
-
if (
|
|
67
|
-
ctx.response.length =
|
|
71
|
+
if (downloadTotal) // preserve this info
|
|
72
|
+
ctx.response.length = downloadTotal;
|
|
68
73
|
ts.once('end', () => // in case of compressed response, we offer calculation of real size
|
|
69
|
-
ctx.state.length = ts.getBytesSent());
|
|
74
|
+
ctx.state.length = ts.getBytesSent() - offset);
|
|
70
75
|
};
|
|
71
76
|
exports.throttler = throttler;
|
|
72
77
|
function roundSpeed(n) {
|
package/src/update.js
CHANGED
|
@@ -15,10 +15,7 @@ const plugins_1 = require("./plugins");
|
|
|
15
15
|
const promises_1 = require("fs/promises");
|
|
16
16
|
const open_1 = __importDefault(require("open"));
|
|
17
17
|
async function getUpdate() {
|
|
18
|
-
|
|
19
|
-
if (latest.name === const_1.VERSION)
|
|
20
|
-
throw "you already have the latest version: " + const_1.VERSION;
|
|
21
|
-
return latest;
|
|
18
|
+
return (await (0, github_1.getRepoInfo)(const_1.HFS_REPO + '/releases?per_page=1'))[0];
|
|
22
19
|
}
|
|
23
20
|
exports.getUpdate = getUpdate;
|
|
24
21
|
const LOCAL_UPDATE = 'hfs-update.zip'; // update from file takes precedence over net
|
package/src/upload.js
CHANGED
|
@@ -3,7 +3,7 @@ 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.uploadWriter = exports.minAvailableMb = exports.deleteUnfinishedUploadsAfter = void 0;
|
|
6
|
+
exports.uploadWriter = exports.getUploadMeta = exports.minAvailableMb = exports.deleteUnfinishedUploadsAfter = void 0;
|
|
7
7
|
const vfs_1 = require("./vfs");
|
|
8
8
|
const const_1 = require("./const");
|
|
9
9
|
const path_1 = require("path");
|
|
@@ -14,11 +14,23 @@ const config_1 = require("./config");
|
|
|
14
14
|
const util_os_1 = require("./util-os");
|
|
15
15
|
const connections_1 = require("./connections");
|
|
16
16
|
const throttler_1 = require("./throttler");
|
|
17
|
-
const
|
|
17
|
+
const perm_1 = require("./perm");
|
|
18
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
|
+
const ATTR_UPLOADER = 'uploader';
|
|
23
|
+
function getUploadMeta(path) {
|
|
24
|
+
return (0, misc_1.loadFileAttr)(path, ATTR_UPLOADER);
|
|
25
|
+
}
|
|
26
|
+
exports.getUploadMeta = getUploadMeta;
|
|
27
|
+
function setUploadMeta(path, ctx) {
|
|
28
|
+
return (0, misc_1.storeFileAttr)(path, ATTR_UPLOADER, {
|
|
29
|
+
username: (0, perm_1.getCurrentUsername)(ctx),
|
|
30
|
+
ip: ctx.ip,
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
// stay sync because we use this function with formidable()
|
|
22
34
|
function uploadWriter(base, path, ctx) {
|
|
23
35
|
if ((0, misc_1.dirTraversal)(path))
|
|
24
36
|
return fail(const_1.HTTP_FOOL);
|
|
@@ -33,21 +45,22 @@ function uploadWriter(base, path, ctx) {
|
|
|
33
45
|
const free = (0, util_os_1.getFreeDiskSync)(dir);
|
|
34
46
|
if (typeof free !== 'number' || isNaN(free))
|
|
35
47
|
throw '';
|
|
36
|
-
if (reqSize >
|
|
48
|
+
if (reqSize > free - (min || 0))
|
|
37
49
|
return fail(const_1.HTTP_PAYLOAD_TOO_LARGE);
|
|
38
50
|
}
|
|
39
|
-
catch (e) {
|
|
51
|
+
catch (e) { // warn, but let it through
|
|
40
52
|
console.warn("can't check disk size:", e.message || String(e));
|
|
41
53
|
}
|
|
42
54
|
if (ctx.query.skipExisting && fs_1.default.existsSync(fullPath))
|
|
43
55
|
return fail(const_1.HTTP_CONFLICT);
|
|
44
|
-
fs_1.default.mkdirSync(dir, { recursive: true })
|
|
56
|
+
if (fs_1.default.mkdirSync(dir, { recursive: true }))
|
|
57
|
+
setUploadMeta(dir, ctx);
|
|
45
58
|
const keepName = (0, path_1.basename)(fullPath).slice(-200);
|
|
46
59
|
let tempName = (0, path_1.join)(dir, 'hfs$upload-' + keepName);
|
|
47
60
|
const resumable = fs_1.default.existsSync(tempName) && tempName;
|
|
48
61
|
if (resumable)
|
|
49
62
|
tempName = (0, path_1.join)(dir, 'hfs$upload2-' + keepName);
|
|
50
|
-
|
|
63
|
+
let resume = Number(ctx.query.resume);
|
|
51
64
|
const size = resumable && (0, misc_1.try_)(() => fs_1.default.statSync(resumable).size);
|
|
52
65
|
if (size === undefined) // stat failed
|
|
53
66
|
return fail(const_1.HTTP_SERVER_ERROR);
|
|
@@ -62,6 +75,8 @@ function uploadWriter(base, path, ctx) {
|
|
|
62
75
|
}));
|
|
63
76
|
}
|
|
64
77
|
const resuming = resume && resumable;
|
|
78
|
+
if (!resuming)
|
|
79
|
+
resume = 0;
|
|
65
80
|
const ret = resuming ? fs_1.default.createWriteStream(resumable, { flags: 'r+', start: resume })
|
|
66
81
|
: fs_1.default.createWriteStream(tempName);
|
|
67
82
|
if (resuming) {
|
|
@@ -70,9 +85,10 @@ function uploadWriter(base, path, ctx) {
|
|
|
70
85
|
}
|
|
71
86
|
cancelDeletion(tempName);
|
|
72
87
|
trackProgress();
|
|
73
|
-
ret.once('close', () => {
|
|
88
|
+
ret.once('close', async () => {
|
|
74
89
|
if (!ctx.req.aborted) {
|
|
75
90
|
let dest = fullPath;
|
|
91
|
+
await setUploadMeta(tempName, ctx);
|
|
76
92
|
if (dontOverwriteUploading.get() && fs_1.default.existsSync(dest)) {
|
|
77
93
|
const ext = (0, path_1.extname)(dest);
|
|
78
94
|
const base = dest.slice(0, -ext.length);
|
|
@@ -102,14 +118,15 @@ function uploadWriter(base, path, ctx) {
|
|
|
102
118
|
if (!conn)
|
|
103
119
|
return () => { };
|
|
104
120
|
ctx.state.uploadPath = ctx.path + path;
|
|
105
|
-
|
|
121
|
+
const opTotal = reqSize + resume;
|
|
122
|
+
(0, connections_1.updateConnection)(conn, { ctx, op: 'upload', opTotal, opOffset: resume / opTotal });
|
|
106
123
|
const h = setInterval(() => {
|
|
107
124
|
const now = Date.now();
|
|
108
125
|
const got = ret.bytesWritten;
|
|
109
126
|
const inSpeed = (0, throttler_1.roundSpeed)((got - lastGot) / (now - lastGotTime));
|
|
110
127
|
lastGot = got;
|
|
111
128
|
lastGotTime = now;
|
|
112
|
-
(0, connections_1.updateConnection)(conn, { inSpeed, got,
|
|
129
|
+
(0, connections_1.updateConnection)(conn, { inSpeed, got, opProgress: (resume + got) / opTotal });
|
|
113
130
|
}, 1000);
|
|
114
131
|
ret.once('close', () => clearInterval(h));
|
|
115
132
|
}
|
package/src/util-files.js
CHANGED
|
@@ -4,9 +4,10 @@ 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.isValidFileName = exports.createFileWithPath = exports.prepareFolder = exports.unzip = exports.dirStream = exports.adjustStaticPathForGlob = exports.isWindowsDrive = exports.dirTraversal = exports.watchDir = exports.readFileBusy = exports.isDirectory = void 0;
|
|
7
|
+
exports.loadFileAttr = exports.storeFileAttr = exports.isValidFileName = exports.createFileWithPath = exports.prepareFolder = exports.unzip = exports.dirStream = exports.adjustStaticPathForGlob = exports.isWindowsDrive = exports.dirTraversal = exports.watchDir = exports.readFileBusy = exports.isDirectory = void 0;
|
|
8
8
|
const promises_1 = __importDefault(require("fs/promises"));
|
|
9
9
|
const misc_1 = require("./misc");
|
|
10
|
+
const util_1 = require("util");
|
|
10
11
|
const fs_1 = require("fs");
|
|
11
12
|
const path_1 = require("path");
|
|
12
13
|
const fast_glob_1 = __importDefault(require("fast-glob"));
|
|
@@ -15,6 +16,8 @@ const util_os_1 = require("./util-os");
|
|
|
15
16
|
const stream_1 = require("stream");
|
|
16
17
|
// @ts-ignore
|
|
17
18
|
const unzip_stream_1 = __importDefault(require("unzip-stream"));
|
|
19
|
+
// @ts-ignore
|
|
20
|
+
const fs_extended_attributes_1 = __importDefault(require("fs-extended-attributes"));
|
|
18
21
|
async function isDirectory(path) {
|
|
19
22
|
try {
|
|
20
23
|
return (await promises_1.default.stat(path)).isDirectory();
|
|
@@ -118,17 +121,19 @@ async function unzip(stream, cb) {
|
|
|
118
121
|
let pending = Promise.resolve();
|
|
119
122
|
return new Promise(resolve => stream.pipe(unzip_stream_1.default.Parse())
|
|
120
123
|
.on('end', () => pending.then(resolve))
|
|
121
|
-
.on('entry', async (
|
|
124
|
+
.on('entry', (entry) => pending = pending.then(async () => {
|
|
122
125
|
const { path, type } = entry;
|
|
123
|
-
const dest = cb(path)
|
|
126
|
+
const dest = await (0, misc_1.try_)(() => cb(path), e => {
|
|
127
|
+
console.warn(String(e));
|
|
128
|
+
return false;
|
|
129
|
+
});
|
|
124
130
|
if (!dest || type !== 'File')
|
|
125
131
|
return entry.autodrain();
|
|
126
|
-
await pending; // don't overlap writings
|
|
127
132
|
console.debug('unzip', dest);
|
|
128
133
|
await prepareFolder(dest);
|
|
129
134
|
const thisFile = entry.pipe((0, fs_1.createWriteStream)(dest));
|
|
130
|
-
|
|
131
|
-
}));
|
|
135
|
+
await (0, stream_1.once)(thisFile, 'finish');
|
|
136
|
+
})));
|
|
132
137
|
}
|
|
133
138
|
exports.unzip = unzip;
|
|
134
139
|
async function prepareFolder(path, dirnameIt = true) {
|
|
@@ -161,3 +166,17 @@ function isValidFileName(name) {
|
|
|
161
166
|
return !/^\.\.?$|[/:*?"<>|\\]/.test(name);
|
|
162
167
|
}
|
|
163
168
|
exports.isValidFileName = isValidFileName;
|
|
169
|
+
const FILE_ATTR_PREFIX = 'user.hfs.'; // user. prefix to be linux compatible
|
|
170
|
+
function storeFileAttr(path, k, v) {
|
|
171
|
+
return (0, util_1.promisify)(fs_extended_attributes_1.default.set)(path, FILE_ATTR_PREFIX + k, JSON.stringify(v))
|
|
172
|
+
.then(() => true, (e) => {
|
|
173
|
+
console.error("couldn't store metadata on", path, String(e.message || e));
|
|
174
|
+
return false;
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
exports.storeFileAttr = storeFileAttr;
|
|
178
|
+
async function loadFileAttr(path, k) {
|
|
179
|
+
var _a;
|
|
180
|
+
return (_a = (0, misc_1.tryJson)(String(await (0, util_1.promisify)(fs_extended_attributes_1.default.get)(path, FILE_ATTR_PREFIX + k)))) !== null && _a !== void 0 ? _a : undefined; // normalize, as we get null instead of undefined on windows
|
|
181
|
+
}
|
|
182
|
+
exports.loadFileAttr = loadFileAttr;
|
package/src/vfs.js
CHANGED
|
@@ -267,20 +267,14 @@ function masksCouldGivePermission(masks, perm) {
|
|
|
267
267
|
}
|
|
268
268
|
exports.masksCouldGivePermission = masksCouldGivePermission;
|
|
269
269
|
function parentMaskApplier(parent) {
|
|
270
|
-
const matchers = Object.entries(parent.masks || {}).map(([k,
|
|
270
|
+
const matchers = (0, misc_1.onlyTruthy)(Object.entries(parent.masks || {}).map(([k, { maskOnly, ...mods }]) => {
|
|
271
271
|
k = k.startsWith('**/') ? k.slice(3) : !k.includes('/') ? k : '';
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
return (item, virtualBasename) => {
|
|
278
|
-
if (virtualBasename === undefined)
|
|
279
|
-
virtualBasename = getNodeName(item);
|
|
280
|
-
for (const entry of matchers) {
|
|
281
|
-
if (!entry)
|
|
272
|
+
return k && { mods, maskOnly, matcher: (0, misc_1.makeMatcher)(k) };
|
|
273
|
+
}));
|
|
274
|
+
return (item, virtualBasename = getNodeName(item)) => {
|
|
275
|
+
for (const { matcher, mods, maskOnly } of matchers) {
|
|
276
|
+
if (maskOnly === 'folders' && !item.isFolder || maskOnly === 'files' && item.isFolder)
|
|
282
277
|
continue;
|
|
283
|
-
const [matcher, mods] = entry;
|
|
284
278
|
if (!matcher(virtualBasename))
|
|
285
279
|
continue;
|
|
286
280
|
if (item.masks)
|
package/src/zip.js
CHANGED
|
@@ -21,7 +21,7 @@ async function zipStreamFromFolder(node, ctx) {
|
|
|
21
21
|
ctx.mime = 'zip';
|
|
22
22
|
// ctx.query.list is undefined | string | string[]
|
|
23
23
|
const list = (_a = (0, misc_1.wantArray)(ctx.query.list)[0]) === null || _a === void 0 ? void 0 : _a.split('*'); // we are using * as separator because it cannot be used in a file name and doesn't need url encoding
|
|
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
|
+
const name = (list === null || list === void 0 ? void 0 : list.length) === 1 ? decodeURIComponent((0, path_1.basename)(list[0])) : (0, vfs_1.getNodeName)(node);
|
|
25
25
|
ctx.attachment(((0, misc_1.isWindowsDrive)(name) ? name[0] : (name || 'archive')) + '.zip');
|
|
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')
|
|
@@ -33,7 +33,7 @@ async function zipStreamFromFolder(node, ctx) {
|
|
|
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
35
|
yield subNode; // it could be empty
|
|
36
|
-
yield* (0, vfs_1.walkNode)(subNode, ctx, Infinity, uri + '/', 'can_read');
|
|
36
|
+
yield* (0, vfs_1.walkNode)(subNode, ctx, Infinity, decodeURI(uri) + '/', 'can_read');
|
|
37
37
|
}
|
|
38
38
|
continue;
|
|
39
39
|
}
|