hfs 0.53.0 → 0.54.0-alpha3
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/admin/assets/index-CcUio6cm.css +1 -0
- package/admin/assets/index-DCsMhd8s.js +807 -0
- package/admin/assets/{sha512-rpja4qx0.js → sha512-VvQJXb6I.js} +1 -1
- package/admin/index.html +4 -2
- package/frontend/assets/index-legacy-vQymU_EM.js +60 -0
- package/frontend/assets/{sha512-legacy-pz5im6D1.js → sha512-legacy-BD2cMJIT.js} +1 -1
- package/frontend/index.html +2 -2
- package/package.json +8 -6
- package/plugins/antibrute/plugin.js +1 -1
- package/plugins/download-counter/plugin.js +0 -15
- package/plugins/list-uploader/public/main.js +3 -2
- package/src/acme.js +2 -2
- package/src/adminApis.js +23 -3
- package/src/api.accounts.js +1 -1
- package/src/api.auth.js +6 -4
- package/src/api.get_file_list.js +18 -9
- package/src/api.monitor.js +1 -1
- package/src/api.net.js +4 -1
- package/src/api.plugins.js +2 -0
- package/src/api.vfs.js +6 -4
- package/src/apiMiddleware.js +4 -1
- package/src/auth.js +7 -7
- package/src/basicWeb.js +1 -1
- package/src/block.js +13 -1
- package/src/commands.js +8 -2
- package/src/const.js +13 -10
- package/src/cross-const.js +2 -1
- package/src/cross.js +25 -8
- package/src/debounceAsync.js +3 -5
- package/src/dirStream.js +138 -0
- package/src/events.js +18 -8
- package/src/frontEndApis.js +32 -15
- package/src/github.js +21 -3
- package/src/langs/embedded.js +4 -1
- package/src/langs/hfs-lang-fi.json +5 -3
- package/src/langs/hfs-lang-hu.json +27 -19
- package/src/langs/hfs-lang-it.json +2 -1
- package/src/langs/hfs-lang-th.json +166 -0
- package/src/langs/hfs-lang-tr.json +166 -0
- package/src/langs/hfs-lang-uk.json +170 -0
- package/src/langs/hfs-lang-vi.json +5 -5
- package/src/listen.js +12 -4
- package/src/log.js +1 -1
- package/src/makeQ.js +31 -0
- package/src/middlewares.js +11 -8
- package/src/misc.js +2 -1
- package/src/nat.js +4 -4
- package/src/perm.js +1 -1
- package/src/persistence.js +2 -1
- package/src/plugins.js +22 -3
- package/src/serveFile.js +16 -14
- package/src/serveGuiAndSharedFiles.js +9 -4
- package/src/serveGuiFiles.js +8 -6
- package/src/srp.js +2 -2
- package/src/update.js +48 -7
- package/src/upload.js +33 -18
- package/src/util-files.js +7 -29
- package/src/vfs.js +23 -11
- package/src/watchLoad.js +1 -1
- package/src/zip.js +4 -4
- package/README.md +0 -232
- package/admin/assets/index-Ly9xr1F6.js +0 -811
- package/admin/assets/index-gNE0rRfC.css +0 -1
- package/frontend/assets/index-legacy-5MEl9EM8.js +0 -60
- /package/frontend/assets/{polyfills-legacy-31b7nyOi.js → polyfills-legacy-DMrMt_pQ.js} +0 -0
package/src/middlewares.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.sessionMiddleware = exports.paramsDecoder = exports.prepareState = exports.getProxyDetected = exports.someSecurity = exports.headRequests = exports.gzipper = exports.sessionDuration = void 0;
|
|
7
|
+
exports.sessionMiddleware = exports.paramsDecoder = exports.prepareState = exports.getProxyDetected = exports.someSecurity = exports.cloudflareDetected = exports.headRequests = exports.gzipper = exports.sessionDuration = void 0;
|
|
8
8
|
const koa_compress_1 = __importDefault(require("koa-compress"));
|
|
9
9
|
const const_1 = require("./const");
|
|
10
10
|
const misc_1 = require("./misc");
|
|
@@ -19,8 +19,10 @@ const config_1 = require("./config");
|
|
|
19
19
|
const koa_session_1 = __importDefault(require("koa-session"));
|
|
20
20
|
const index_1 = require("./index");
|
|
21
21
|
const events_1 = __importDefault(require("./events"));
|
|
22
|
+
const allowSessionIpChange = (0, config_1.defineConfig)('allow_session_ip_change', false);
|
|
22
23
|
const forceHttps = (0, config_1.defineConfig)('force_https', true);
|
|
23
24
|
const ignoreProxies = (0, config_1.defineConfig)('ignore_proxies', false);
|
|
25
|
+
const allowAuthorizationHeader = (0, config_1.defineConfig)('authorization_header', true);
|
|
24
26
|
exports.sessionDuration = (0, config_1.defineConfig)('session_duration', Number(process.env.SESSION_DURATION) || misc_1.DAY / 1000, v => v * 1000);
|
|
25
27
|
exports.gzipper = (0, koa_compress_1.default)({
|
|
26
28
|
threshold: 2048,
|
|
@@ -49,9 +51,8 @@ exports.headRequests = headRequests;
|
|
|
49
51
|
let proxyDetected;
|
|
50
52
|
const someSecurity = (ctx, next) => {
|
|
51
53
|
ctx.request.ip = (0, connections_1.normalizeIp)(ctx.ip);
|
|
52
|
-
// don't allow sessions to change ip
|
|
53
54
|
const ss = ctx.session;
|
|
54
|
-
if (ss === null || ss === void 0 ? void 0 : ss.username)
|
|
55
|
+
if ((ss === null || ss === void 0 ? void 0 : ss.username) && (!allowSessionIpChange.get() || !ctx.secure && allowSessionIpChange.get() === 'https'))
|
|
55
56
|
if (!ss.ip)
|
|
56
57
|
ss.ip = ctx.ip;
|
|
57
58
|
else if (ss.ip !== ctx.ip) {
|
|
@@ -69,6 +70,8 @@ const someSecurity = (ctx, next) => {
|
|
|
69
70
|
proxyDetected = ctx;
|
|
70
71
|
ctx.state.whenProxyDetected = new Date();
|
|
71
72
|
}
|
|
73
|
+
if (ctx.get('cf-ray'))
|
|
74
|
+
exports.cloudflareDetected = new Date();
|
|
72
75
|
}
|
|
73
76
|
catch (_a) {
|
|
74
77
|
return ctx.status = const_1.HTTP_FOOL;
|
|
@@ -112,24 +115,24 @@ const prepareState = async (ctx, next) => {
|
|
|
112
115
|
return;
|
|
113
116
|
const [u, p] = (0, misc_1.splitAt)(':', String(login));
|
|
114
117
|
ctx.redirect(ctx.originalUrl.slice(0, -ctx.querystring.length - 1)); // redirect to hide credentials
|
|
115
|
-
return doLogin(u, p);
|
|
118
|
+
return doLogin(u, p, 'url');
|
|
116
119
|
}
|
|
117
120
|
function getHttpAccount() {
|
|
118
121
|
var _a;
|
|
119
|
-
const b64 = (_a = ctx.get('authorization')) === null || _a === void 0 ? void 0 : _a.split(' ')[1];
|
|
122
|
+
const b64 = allowAuthorizationHeader.get() && ((_a = ctx.get('authorization')) === null || _a === void 0 ? void 0 : _a.split(' ')[1]);
|
|
120
123
|
if (!b64)
|
|
121
124
|
return;
|
|
122
125
|
try {
|
|
123
126
|
const [u, p] = atob(b64).split(':');
|
|
124
|
-
return doLogin(u, p || '');
|
|
127
|
+
return doLogin(u, p || '', 'header');
|
|
125
128
|
}
|
|
126
129
|
catch (_b) { }
|
|
127
130
|
}
|
|
128
|
-
async function doLogin(u, p) {
|
|
131
|
+
async function doLogin(u, p, via) {
|
|
129
132
|
var _a;
|
|
130
133
|
if (!u || u === ((_a = ctx.session) === null || _a === void 0 ? void 0 : _a.username))
|
|
131
134
|
return; // providing credentials, but not needed
|
|
132
|
-
await events_1.default.emitAsync('attemptingLogin', ctx);
|
|
135
|
+
await events_1.default.emitAsync('attemptingLogin', { ctx, username: u, via });
|
|
133
136
|
const a = await (0, auth_1.srpCheck)(u, p);
|
|
134
137
|
if (a) {
|
|
135
138
|
await (0, auth_1.setLoggedIn)(ctx, a.username);
|
package/src/misc.js
CHANGED
|
@@ -168,13 +168,14 @@ function createStreamLimiter(limit) {
|
|
|
168
168
|
}
|
|
169
169
|
exports.createStreamLimiter = createStreamLimiter;
|
|
170
170
|
async function deleteNode(ctx, node, uri) {
|
|
171
|
+
var _a;
|
|
171
172
|
const { source } = node;
|
|
172
173
|
if (!source)
|
|
173
174
|
return const_1.HTTP_METHOD_NOT_ALLOWED;
|
|
174
175
|
if ((0, vfs_1.statusCodeForMissingPerm)(node, 'can_delete', ctx))
|
|
175
176
|
return ctx.status;
|
|
176
177
|
try {
|
|
177
|
-
if (await events_1.default.emitAsync('deleting', { node, ctx }).isDefaultPrevented())
|
|
178
|
+
if ((_a = (await events_1.default.emitAsync('deleting', { node, ctx }))) === null || _a === void 0 ? void 0 : _a.isDefaultPrevented())
|
|
178
179
|
return null; // stop
|
|
179
180
|
ctx.logExtra(null, { target: decodeURI(uri) });
|
|
180
181
|
await (0, promises_1.rm)(source, { recursive: true });
|
package/src/nat.js
CHANGED
|
@@ -32,7 +32,7 @@ exports.defaultBaseUrl = (0, valtio_1.proxy)({
|
|
|
32
32
|
exports.upnpClient = new nat_upnp_rejetto_1.Client({ timeout: 4000 });
|
|
33
33
|
const originalMethod = exports.upnpClient.getGateway;
|
|
34
34
|
// other client methods call getGateway too, so this will ensure they reuse this same result
|
|
35
|
-
exports.upnpClient.getGateway = (0, debounceAsync_1.debounceAsync)(() => originalMethod.apply(exports.upnpClient),
|
|
35
|
+
exports.upnpClient.getGateway = (0, debounceAsync_1.debounceAsync)(() => originalMethod.apply(exports.upnpClient), { retain: cross_1.HOUR, retainFailure: 30000 });
|
|
36
36
|
exports.upnpClient.getGateway().then(res => {
|
|
37
37
|
console.debug('upnp', res.gateway.description);
|
|
38
38
|
}, e => console.debug('upnp failed:', e.message || String(e)));
|
|
@@ -64,16 +64,16 @@ exports.getPublicIps = (0, debounceAsync_1.debounceAsync)(async () => {
|
|
|
64
64
|
return validIps;
|
|
65
65
|
}))));
|
|
66
66
|
return exports.defaultBaseUrl.publicIps = lodash_1.default.uniq(ips.flat());
|
|
67
|
-
},
|
|
67
|
+
}, { retain: 10 * cross_1.MINUTE });
|
|
68
68
|
exports.getNatInfo = (0, debounceAsync_1.debounceAsync)(async () => {
|
|
69
69
|
var _a, _b, _c, _d;
|
|
70
|
+
const gatewayIpPromise = findGateway().catch(() => undefined);
|
|
70
71
|
const res = await (0, cross_1.haveTimeout)(10000, exports.upnpClient.getGateway()).catch(() => null);
|
|
71
72
|
const status = await (0, listen_1.getServerStatus)();
|
|
72
73
|
const mappings = res && await (0, cross_1.haveTimeout)(5000, exports.upnpClient.getMappings()).catch(() => null);
|
|
73
74
|
console.debug('mappings found', mappings === null || mappings === void 0 ? void 0 : mappings.map(x => x.description));
|
|
74
|
-
const gatewayIp = res && (0, cross_1.try_)(() => new URL(res.gateway.description).hostname, () => { var _a; return console.debug('unexpected upnp gw', (_a = res.gateway) === null || _a === void 0 ? void 0 : _a.description); })
|
|
75
|
-
|| await findGateway().catch(() => undefined);
|
|
76
75
|
const localIps = await (0, listen_1.getIps)(false);
|
|
76
|
+
const gatewayIp = await gatewayIpPromise;
|
|
77
77
|
const localIp = (res === null || res === void 0 ? void 0 : res.address) || gatewayIp ? lodash_1.default.maxBy(localIps, x => (0, cross_1.inCommon)(x, gatewayIp)) : localIps[0];
|
|
78
78
|
const internalPort = ((_a = status === null || status === void 0 ? void 0 : status.https) === null || _a === void 0 ? void 0 : _a.listening) && status.https.port || ((_b = status === null || status === void 0 ? void 0 : status.http) === null || _b === void 0 ? void 0 : _b.listening) && status.http.port || undefined;
|
|
79
79
|
const mapped = lodash_1.default.find(mappings, x => x.private.host === localIp && x.private.port === internalPort);
|
package/src/perm.js
CHANGED
|
@@ -47,7 +47,7 @@ createAdminConfig.sub(v => {
|
|
|
47
47
|
});
|
|
48
48
|
async function createAdmin(password, username = 'admin') {
|
|
49
49
|
const acc = await addAccount(username, { admin: true, password }, true);
|
|
50
|
-
console.log(acc ? "account admin
|
|
50
|
+
console.log(acc ? "account admin set" : "something went wrong");
|
|
51
51
|
}
|
|
52
52
|
exports.createAdmin = createAdmin;
|
|
53
53
|
const srp6aNimbusRoutines = new tssrp6a_1.SRPRoutines(new tssrp6a_1.SRPParameters());
|
package/src/persistence.js
CHANGED
|
@@ -8,7 +8,8 @@ exports.storedMap = new kvstorage_1.KvStorage({
|
|
|
8
8
|
defaultPutDelay: 5000,
|
|
9
9
|
maxPutDelay: misc_1.MINUTE,
|
|
10
10
|
maxPutDelayCreate: 1000,
|
|
11
|
-
rewriteLater: true
|
|
11
|
+
rewriteLater: true,
|
|
12
|
+
bucketThreshold: 10000,
|
|
12
13
|
});
|
|
13
14
|
exports.storedMap.open('data.kv');
|
|
14
15
|
(0, first_1.onProcessExit)(() => exports.storedMap.flush());
|
package/src/plugins.js
CHANGED
|
@@ -27,13 +27,14 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
27
27
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
28
28
|
};
|
|
29
29
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
30
|
-
exports.getMissingDependencies = exports.parsePluginSource = exports.rescan = exports.pluginsConfig = exports.enablePlugins = exports.pluginsWatcher = exports.getAvailablePlugins = exports.mapPlugins = exports.SERVER_CODE_ID = exports.Plugin = exports.pluginsMiddleware = exports.getPluginConfigFields = exports.findPluginByRepo = exports.getPluginInfo = exports.setPluginConfig = exports.startPlugin = exports.stopPlugin = exports.enablePlugin = exports.isPluginEnabled = exports.isPluginRunning = exports.STORAGE_FOLDER = exports.DISABLING_SUFFIX = exports.PATH = void 0;
|
|
30
|
+
exports.getMissingDependencies = exports.parsePluginSource = exports.rescan = exports.pluginsConfig = exports.enablePlugins = exports.pluginsWatcher = exports.getAvailablePlugins = exports.firstPlugin = exports.mapPlugins = exports.SERVER_CODE_ID = exports.Plugin = exports.pluginsMiddleware = exports.getPluginConfigFields = exports.findPluginByRepo = exports.getPluginInfo = exports.setPluginConfig = exports.startPlugin = exports.stopPlugin = exports.enablePlugin = exports.isPluginEnabled = exports.isPluginRunning = exports.STORAGE_FOLDER = exports.DISABLING_SUFFIX = exports.PATH = void 0;
|
|
31
31
|
const fast_glob_1 = __importDefault(require("fast-glob"));
|
|
32
32
|
const watchLoad_1 = require("./watchLoad");
|
|
33
33
|
const lodash_1 = __importDefault(require("lodash"));
|
|
34
34
|
const const_1 = require("./const");
|
|
35
35
|
const Const = __importStar(require("./const"));
|
|
36
36
|
const misc_1 = require("./misc");
|
|
37
|
+
const misc = __importStar(require("./misc"));
|
|
37
38
|
const config_1 = require("./config");
|
|
38
39
|
const serveFile_1 = require("./serveFile");
|
|
39
40
|
const events_1 = __importDefault(require("./events"));
|
|
@@ -46,6 +47,7 @@ const kvstorage_1 = require("@rejetto/kvstorage");
|
|
|
46
47
|
const first_1 = require("./first");
|
|
47
48
|
const frontEndApis_1 = require("./frontEndApis");
|
|
48
49
|
const index_1 = require("./index");
|
|
50
|
+
const block_1 = require("./block");
|
|
49
51
|
exports.PATH = 'plugins';
|
|
50
52
|
exports.DISABLING_SUFFIX = '-disabled';
|
|
51
53
|
exports.STORAGE_FOLDER = 'storage';
|
|
@@ -134,6 +136,8 @@ async function initPlugin(pl, morePassedToInit) {
|
|
|
134
136
|
getHfsConfig: config_1.getConfig,
|
|
135
137
|
customApiCall,
|
|
136
138
|
notifyClient: frontEndApis_1.notifyClient,
|
|
139
|
+
addBlock: block_1.addBlock,
|
|
140
|
+
misc,
|
|
137
141
|
...morePassedToInit
|
|
138
142
|
})));
|
|
139
143
|
}
|
|
@@ -159,7 +163,7 @@ const pluginsMiddleware = async (ctx, next) => {
|
|
|
159
163
|
lastStatus = ctx.status;
|
|
160
164
|
lastBody = ctx.body;
|
|
161
165
|
}
|
|
162
|
-
if (res === true && !ctx.isStopped) { //
|
|
166
|
+
if (res === true && !ctx.isStopped) { //legacy pre-0.53
|
|
163
167
|
ctx.stop();
|
|
164
168
|
warnOnce(`plugin ${id} is using deprecated API (return true on middleware) and may not work with future versions (check for an update to "${id}")`);
|
|
165
169
|
}
|
|
@@ -306,12 +310,27 @@ function mapPlugins(cb, includeServerCode = true) {
|
|
|
306
310
|
}).filter(x => x !== undefined);
|
|
307
311
|
}
|
|
308
312
|
exports.mapPlugins = mapPlugins;
|
|
313
|
+
function firstPlugin(cb, includeServerCode = true) {
|
|
314
|
+
for (const [plName, pl] of Object.entries(plugins)) {
|
|
315
|
+
if (!includeServerCode && plName === exports.SERVER_CODE_ID)
|
|
316
|
+
continue;
|
|
317
|
+
try {
|
|
318
|
+
const ret = cb(pl, plName);
|
|
319
|
+
if (ret !== undefined)
|
|
320
|
+
return ret;
|
|
321
|
+
}
|
|
322
|
+
catch (e) {
|
|
323
|
+
console.log('plugin error', plName, String(e));
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
exports.firstPlugin = firstPlugin;
|
|
309
328
|
let availablePlugins = {};
|
|
310
329
|
function getAvailablePlugins() {
|
|
311
330
|
return Object.values(availablePlugins);
|
|
312
331
|
}
|
|
313
332
|
exports.getAvailablePlugins = getAvailablePlugins;
|
|
314
|
-
const rescanAsap = (0, misc_1.debounceAsync)(rescan, 1000);
|
|
333
|
+
const rescanAsap = (0, misc_1.debounceAsync)(rescan, { wait: 1000 });
|
|
315
334
|
if (!(0, fs_1.existsSync)(exports.PATH))
|
|
316
335
|
try {
|
|
317
336
|
(0, fs_1.mkdirSync)(exports.PATH);
|
package/src/serveFile.js
CHANGED
|
@@ -19,9 +19,9 @@ const auth_1 = require("./auth");
|
|
|
19
19
|
const errorPages_1 = require("./errorPages");
|
|
20
20
|
const stream_1 = require("stream");
|
|
21
21
|
const allowedReferer = (0, config_1.defineConfig)('allowed_referer', '');
|
|
22
|
-
const
|
|
23
|
-
const
|
|
24
|
-
const
|
|
22
|
+
const maxDownloads = downloadLimiter((0, config_1.defineConfig)(misc_1.CFG.max_downloads, 0), () => true);
|
|
23
|
+
const maxDownloadsPerIp = downloadLimiter((0, config_1.defineConfig)(misc_1.CFG.max_downloads_per_ip, 0), ctx => ctx.ip);
|
|
24
|
+
const maxDownloadsPerAccount = downloadLimiter((0, config_1.defineConfig)(misc_1.CFG.max_downloads_per_account, 0), ctx => (0, auth_1.getCurrentUsername)(ctx) || undefined);
|
|
25
25
|
async function serveFileNode(ctx, node) {
|
|
26
26
|
var _a, _b;
|
|
27
27
|
const { source, mime } = node;
|
|
@@ -30,7 +30,7 @@ async function serveFileNode(ctx, node) {
|
|
|
30
30
|
: lodash_1.default.find(mime, (val, mask) => (0, misc_1.matches)(name, mask));
|
|
31
31
|
if (allowedReferer.get()) {
|
|
32
32
|
const ref = (_a = /\/\/([^:/]+)/.exec(ctx.get('referer'))) === null || _a === void 0 ? void 0 : _a[1]; // extract host from url
|
|
33
|
-
if (ref && ref !== host() //
|
|
33
|
+
if (ref && ref !== host() // automatically accept if referer is basically the hosting domain
|
|
34
34
|
&& !(0, misc_1.matches)(ref, allowedReferer.get()))
|
|
35
35
|
return ctx.status = const_1.HTTP_FORBIDDEN;
|
|
36
36
|
}
|
|
@@ -41,10 +41,10 @@ async function serveFileNode(ctx, node) {
|
|
|
41
41
|
else if (((_b = ctx.get('referer')) === null || _b === void 0 ? void 0 : _b.endsWith('/')) && (0, misc_1.with_)(ctx.get('accept'), x => x && !x.includes('text')))
|
|
42
42
|
ctx.state.considerAsGui = true;
|
|
43
43
|
await serveFile(ctx, source || '', mimeString);
|
|
44
|
-
if (await
|
|
45
|
-
await
|
|
44
|
+
if (await maxDownloadsPerAccount(ctx) === undefined) // returning false will not execute other limits
|
|
45
|
+
await maxDownloads(ctx) || await maxDownloadsPerIp(ctx);
|
|
46
46
|
function host() {
|
|
47
|
-
const s = ctx.
|
|
47
|
+
const s = ctx.host;
|
|
48
48
|
return s[0] === '[' ? s.slice(1, s.indexOf(']')) : s === null || s === void 0 ? void 0 : s.split(':')[0];
|
|
49
49
|
}
|
|
50
50
|
}
|
|
@@ -54,6 +54,8 @@ const mimeCfg = (0, config_1.defineConfig)('mime', { '*': const_1.MIME_AUTO }, o
|
|
|
54
54
|
const values = Object.values(obj);
|
|
55
55
|
return (name) => values[matchers.findIndex(matcher => matcher(name))];
|
|
56
56
|
});
|
|
57
|
+
// after this number of seconds, the browser should check the server to see if there's a newer version of the file
|
|
58
|
+
const cacheControlDiskFiles = (0, config_1.defineConfig)('cache_control_disk_files', 5);
|
|
57
59
|
async function serveFile(ctx, source, mime, content) {
|
|
58
60
|
if (!source)
|
|
59
61
|
return;
|
|
@@ -75,19 +77,20 @@ async function serveFile(ctx, source, mime, content) {
|
|
|
75
77
|
if (!stats.isFile())
|
|
76
78
|
return ctx.status = const_1.HTTP_METHOD_NOT_ALLOWED;
|
|
77
79
|
ctx.set('Last-Modified', stats.mtime.toUTCString());
|
|
78
|
-
ctx.fileSource =
|
|
79
|
-
|
|
80
|
-
ctx.fileStats = // legacy pre-0.51
|
|
81
|
-
ctx.state.fileStats = stats;
|
|
80
|
+
ctx.state.fileSource = source;
|
|
81
|
+
ctx.state.fileStats = stats;
|
|
82
82
|
ctx.status = const_1.HTTP_OK;
|
|
83
83
|
if (ctx.fresh)
|
|
84
84
|
return ctx.status = const_1.HTTP_NOT_MODIFIED;
|
|
85
85
|
if (content !== undefined)
|
|
86
86
|
return ctx.body = content;
|
|
87
|
+
const cc = cacheControlDiskFiles.get();
|
|
88
|
+
if (lodash_1.default.isNumber(cc))
|
|
89
|
+
ctx.set('Cache-Control', `max-age=${cc}`);
|
|
87
90
|
const { size } = stats;
|
|
88
91
|
const range = applyRange(ctx, size);
|
|
89
92
|
ctx.body = (0, fs_1.createReadStream)(source, range);
|
|
90
|
-
if (ctx.vfsNode)
|
|
93
|
+
if (ctx.state.vfsNode)
|
|
91
94
|
monitorAsDownload(ctx, size, range === null || range === void 0 ? void 0 : range.start);
|
|
92
95
|
}
|
|
93
96
|
catch (e) {
|
|
@@ -101,7 +104,6 @@ function monitorAsDownload(ctx, size, offset) {
|
|
|
101
104
|
const conn = (0, connections_1.getConnection)(ctx);
|
|
102
105
|
ctx.body.on('end', () => (0, connections_1.updateConnection)(conn, {}, { opProgress: 1 }));
|
|
103
106
|
(0, connections_1.updateConnection)(conn, {}, {
|
|
104
|
-
op: 'download',
|
|
105
107
|
opProgress: 0,
|
|
106
108
|
opTotal: size,
|
|
107
109
|
opOffset: size && offset && (offset / size),
|
|
@@ -145,7 +147,7 @@ function downloadLimiter(configMax, cbKey) {
|
|
|
145
147
|
const map = new Map();
|
|
146
148
|
return (ctx) => {
|
|
147
149
|
if (!ctx.body || ctx.state.considerAsGui)
|
|
148
|
-
return; // no file sent, cache hit
|
|
150
|
+
return; // !body = no file sent, cache hit
|
|
149
151
|
const k = cbKey(ctx);
|
|
150
152
|
if (k === undefined)
|
|
151
153
|
return; // undefined = skip limit
|
|
@@ -26,7 +26,6 @@ const serveFrontendPrefixed = (0, koa_mount_1.default)(cross_const_1.FRONTEND_UR
|
|
|
26
26
|
const serveAdminFiles = (0, serveGuiFiles_1.serveGuiFiles)(process.env.ADMIN_PROXY, cross_const_1.ADMIN_URI);
|
|
27
27
|
const serveAdminPrefixed = (0, koa_mount_1.default)(cross_const_1.ADMIN_URI.slice(0, -1), serveAdminFiles);
|
|
28
28
|
const serveGuiAndSharedFiles = async (ctx, next) => {
|
|
29
|
-
var _a;
|
|
30
29
|
const { path } = ctx;
|
|
31
30
|
// dynamic import on frontend|admin (used for non-https login) while developing (vite4) is not producing a relative path
|
|
32
31
|
if (const_1.DEV && path.startsWith('/node_modules/')) {
|
|
@@ -100,15 +99,21 @@ const serveGuiAndSharedFiles = async (ctx, next) => {
|
|
|
100
99
|
return;
|
|
101
100
|
}
|
|
102
101
|
const { get } = ctx.query;
|
|
103
|
-
if (node.default && path.endsWith('/') && !get) // final/ needed on browser to make resource urls correctly with html pages
|
|
104
|
-
|
|
102
|
+
if (node.default && path.endsWith('/') && !get) { // final/ needed on browser to make resource urls correctly with html pages
|
|
103
|
+
const found = await (0, vfs_1.urlToNode)(node.default, ctx, node);
|
|
104
|
+
if (found && /\.html?/i.test(node.default))
|
|
105
|
+
ctx.state.considerAsGui = true;
|
|
106
|
+
node = found !== null && found !== void 0 ? found : node;
|
|
107
|
+
}
|
|
108
|
+
if (get === 'icon')
|
|
109
|
+
return (0, serveFile_1.serveFile)(ctx, node.icon || '|'); // pipe to cause not-found
|
|
105
110
|
if (!await (0, vfs_1.nodeIsDirectory)(node))
|
|
106
111
|
return node.url ? ctx.redirect(node.url)
|
|
107
112
|
: !node.source ? (0, errorPages_1.sendErrorPage)(ctx, cross_const_1.HTTP_METHOD_NOT_ALLOWED) // !dir && !source is not supported at this moment
|
|
108
113
|
: !(0, vfs_1.statusCodeForMissingPerm)(node, 'can_read', ctx) ? (0, serveFile_1.serveFileNode)(ctx, node) // all good
|
|
109
114
|
: ctx.status !== cross_const_1.HTTP_UNAUTHORIZED ? null // all errors don't need extra handling, except unauthorized
|
|
110
115
|
: (0, basicWeb_1.detectBasicAgent)(ctx) ? (ctx.set('WWW-Authenticate', 'Basic'), (0, errorPages_1.sendErrorPage)(ctx))
|
|
111
|
-
: (ctx.state.serveApp = true) && serveFrontendFiles(ctx, next);
|
|
116
|
+
: ctx.query.dl === undefined && (ctx.state.serveApp = true) && serveFrontendFiles(ctx, next);
|
|
112
117
|
if (!path.endsWith('/'))
|
|
113
118
|
return ctx.redirect(ctx.state.revProxyPath + ctx.originalUrl.replace(/(\?|$)/, '/$1')); // keep query-string, if any
|
|
114
119
|
if ((0, vfs_1.statusCodeForMissingPerm)(node, 'can_list', ctx)) {
|
package/src/serveGuiFiles.js
CHANGED
|
@@ -43,6 +43,7 @@ const lodash_1 = __importDefault(require("lodash"));
|
|
|
43
43
|
const config_1 = require("./config");
|
|
44
44
|
const lang_1 = require("./lang");
|
|
45
45
|
const upload_1 = require("./upload");
|
|
46
|
+
const splitUploads = (0, config_1.defineConfig)(misc_1.CFG.split_uploads, 0);
|
|
46
47
|
exports.logGui = (0, config_1.defineConfig)(misc_1.CFG.log_gui, false);
|
|
47
48
|
lodash_1.default.each(misc_1.FRONTEND_OPTIONS, (v, k) => (0, config_1.defineConfig)(k, v)); // define default values
|
|
48
49
|
// in case of dev env we have our static files within the 'dist' folder'
|
|
@@ -85,7 +86,7 @@ function adjustBundlerLinks(ctx, uri, data) {
|
|
|
85
86
|
const getFaviconTimestamp = (0, misc_1.debounceAsync)(async () => {
|
|
86
87
|
const f = adminApis_1.favicon.get();
|
|
87
88
|
return !f ? 0 : promises_1.default.stat(f).then(x => (x === null || x === void 0 ? void 0 : x.mtimeMs) || 0, () => 0);
|
|
88
|
-
},
|
|
89
|
+
}, { retain: 5000 });
|
|
89
90
|
async function treatIndex(ctx, filesUri, body) {
|
|
90
91
|
const session = await (0, api_auth_1.refresh_session)({}, ctx);
|
|
91
92
|
ctx.set('etag', '');
|
|
@@ -114,10 +115,6 @@ async function treatIndex(ctx, filesUri, body) {
|
|
|
114
115
|
const isOpen = !isClose;
|
|
115
116
|
if (isHead && isOpen)
|
|
116
117
|
return all + `
|
|
117
|
-
${isFrontend && `
|
|
118
|
-
<title>${adminApis_1.title.get()}</title>
|
|
119
|
-
<link rel="shortcut icon" href="/favicon.ico?${timestamp}" />
|
|
120
|
-
` + (0, customHtml_1.getSection)('htmlHead')}
|
|
121
118
|
<script>
|
|
122
119
|
HFS = ${JSON.stringify({
|
|
123
120
|
VERSION: const_1.VERSION,
|
|
@@ -128,13 +125,18 @@ async function treatIndex(ctx, filesUri, body) {
|
|
|
128
125
|
loadScripts: Object.fromEntries((0, plugins_1.mapPlugins)((p, id) => { var _a; return [id, (_a = p.frontend_js) === null || _a === void 0 ? void 0 : _a.map(f => f.includes('//') ? f : pub + id + '/' + f)]; })),
|
|
129
126
|
prefixUrl: ctx.state.revProxyPath,
|
|
130
127
|
dontOverwriteUploading: upload_1.dontOverwriteUploading.get(),
|
|
128
|
+
splitUploads: splitUploads.get(),
|
|
131
129
|
forceTheme: (0, plugins_1.mapPlugins)(p => lodash_1.default.isString(p.isTheme) ? p.isTheme : undefined).find(Boolean),
|
|
132
130
|
customHtml: lodash_1.default.omit((0, customHtml_1.getAllSections)(), ['top', 'bottom', 'htmlHead', 'style']),
|
|
133
131
|
...(0, misc_1.newObj)(misc_1.FRONTEND_OPTIONS, (v, k) => (0, config_1.getConfig)(k)),
|
|
134
132
|
lang
|
|
135
133
|
}, null, 4).replace(/<(\/script)/g, '<"+"$1') /*avoid breaking our script container*/}
|
|
136
|
-
document.documentElement.setAttribute('ver',
|
|
134
|
+
document.documentElement.setAttribute('ver', HFS.VERSION.split('-')[0])
|
|
137
135
|
</script>
|
|
136
|
+
${isFrontend && `
|
|
137
|
+
<title>${adminApis_1.title.get()}</title>
|
|
138
|
+
<link rel="shortcut icon" href="/favicon.ico?${timestamp}" />
|
|
139
|
+
${(0, customHtml_1.getSection)('htmlHead')}`}
|
|
138
140
|
`;
|
|
139
141
|
if (isBody && isOpen)
|
|
140
142
|
return `${all}
|
package/src/srp.js
CHANGED
|
@@ -15,8 +15,8 @@ async function srpClientSequence(username, password, apiCall) {
|
|
|
15
15
|
exports.srpClientSequence = srpClientSequence;
|
|
16
16
|
async function srpClientPart(username, password, salt, pubKey) {
|
|
17
17
|
const srp6aNimbusRoutines = new tssrp6a_1.SRPRoutines(new tssrp6a_1.SRPParameters());
|
|
18
|
-
const
|
|
19
|
-
const res = await
|
|
18
|
+
const srpClient = new tssrp6a_1.SRPClientSession(srp6aNimbusRoutines);
|
|
19
|
+
const res = await srpClient.step1(username, password);
|
|
20
20
|
return await res.step2(BigInt(salt), BigInt(pubKey));
|
|
21
21
|
}
|
|
22
22
|
exports.srpClientPart = srpClientPart;
|
package/src/update.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.update = exports.updateSupported = exports.localUpdateAvailable = exports.getUpdates = void 0;
|
|
7
|
+
exports.update = exports.updateSupported = exports.localUpdateAvailable = exports.getUpdates = exports.autoCheckUpdateResult = void 0;
|
|
8
8
|
const github_1 = require("./github");
|
|
9
9
|
const const_1 = require("./const");
|
|
10
10
|
const path_1 = require("path");
|
|
@@ -17,15 +17,48 @@ const open_1 = __importDefault(require("open"));
|
|
|
17
17
|
const config_1 = require("./config");
|
|
18
18
|
const util_os_1 = require("./util-os");
|
|
19
19
|
const first_1 = require("./first");
|
|
20
|
+
const persistence_1 = require("./persistence");
|
|
21
|
+
const lodash_1 = __importDefault(require("lodash"));
|
|
20
22
|
const updateToBeta = (0, config_1.defineConfig)('update_to_beta', false);
|
|
23
|
+
const autoCheckUpdate = (0, config_1.defineConfig)('auto_check_update', true);
|
|
24
|
+
const lastCheckUpdate = persistence_1.storedMap.singleSync('lastCheckUpdate', 0);
|
|
25
|
+
const AUTO_CHECK_EVERY = misc_1.DAY;
|
|
26
|
+
exports.autoCheckUpdateResult = persistence_1.storedMap.singleSync('autoCheckUpdateResult', undefined);
|
|
27
|
+
exports.autoCheckUpdateResult.ready().then(() => {
|
|
28
|
+
exports.autoCheckUpdateResult.set(v => {
|
|
29
|
+
if (!v)
|
|
30
|
+
return; // refresh isNewer, as currentVersion may have changed
|
|
31
|
+
v.isNewer = config_1.currentVersion.olderThan(v.tag_name);
|
|
32
|
+
return v;
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
setInterval((0, misc_1.debounceAsync)(async () => {
|
|
36
|
+
if (!autoCheckUpdate.get())
|
|
37
|
+
return;
|
|
38
|
+
if (Date.now() < lastCheckUpdate.get() + AUTO_CHECK_EVERY)
|
|
39
|
+
return;
|
|
40
|
+
console.log("checking for updates");
|
|
41
|
+
try {
|
|
42
|
+
const u = (await getUpdates(true))[0];
|
|
43
|
+
if (u)
|
|
44
|
+
console.log("new version available", u.name);
|
|
45
|
+
exports.autoCheckUpdateResult.set(u);
|
|
46
|
+
lastCheckUpdate.set(Date.now());
|
|
47
|
+
}
|
|
48
|
+
catch (_a) { }
|
|
49
|
+
}), misc_1.HOUR);
|
|
50
|
+
const ReleaseKeys = ['prerelease', 'tag_name', 'name', 'body', 'assets', 'isNewer'];
|
|
51
|
+
const ReleaseAssetKeys = ['name', 'browser_download_url'];
|
|
21
52
|
async function getUpdates(strict = false) {
|
|
53
|
+
(0, github_1.getProjectInfo)(); // check for alerts
|
|
22
54
|
const stable = await (0, github_1.getRepoInfo)(const_1.HFS_REPO + '/releases/latest');
|
|
23
55
|
const verStable = ver(stable);
|
|
24
56
|
const ret = await getBetas();
|
|
25
57
|
stable.isNewer = config_1.currentVersion.olderThan(stable.tag_name);
|
|
26
58
|
if (stable.isNewer || const_1.RUNNING_BETA)
|
|
27
59
|
ret.push(stable);
|
|
28
|
-
|
|
60
|
+
// prune a bit, as it will be serialized, but it has a lot of unused data
|
|
61
|
+
return ret.filter(x => !strict || x.isNewer).map(x => Object.assign(lodash_1.default.pick(x, ReleaseKeys), { assets: x.assets.map(a => lodash_1.default.pick(a, ReleaseAssetKeys)) }));
|
|
29
62
|
function ver(x) {
|
|
30
63
|
return (0, config_1.versionToScalar)(x.name);
|
|
31
64
|
}
|
|
@@ -41,10 +74,10 @@ async function getUpdates(strict = false) {
|
|
|
41
74
|
break;
|
|
42
75
|
const curV = config_1.currentVersion.getScalar();
|
|
43
76
|
for (const x of res) {
|
|
44
|
-
if (!x.prerelease)
|
|
45
|
-
continue;
|
|
77
|
+
if (!x.prerelease || x.name.endsWith('-ignore'))
|
|
78
|
+
continue;
|
|
46
79
|
const v = ver(x);
|
|
47
|
-
if (v
|
|
80
|
+
if (v < verStable) // we don't consider betas before stable
|
|
48
81
|
return ret;
|
|
49
82
|
if (v === curV)
|
|
50
83
|
continue; // skip current
|
|
@@ -68,8 +101,9 @@ exports.updateSupported = updateSupported;
|
|
|
68
101
|
async function update(tagOrUrl = '') {
|
|
69
102
|
if (!await updateSupported())
|
|
70
103
|
throw "only binary versions supports automatic update for now";
|
|
104
|
+
let doingLocal = '';
|
|
71
105
|
let updateSource = tagOrUrl.includes('://') ? await (0, misc_1.httpStream)(tagOrUrl)
|
|
72
|
-
: await localUpdateAvailable() && (0, fs_1.createReadStream)(LOCAL_UPDATE);
|
|
106
|
+
: await localUpdateAvailable() && (0, fs_1.createReadStream)(doingLocal = LOCAL_UPDATE);
|
|
73
107
|
if (!updateSource) {
|
|
74
108
|
if (/^\d/.test(tagOrUrl)) // work even if the tag is passed without the initial 'v' (useful for console commands)
|
|
75
109
|
tagOrUrl = 'v' + tagOrUrl;
|
|
@@ -115,7 +149,14 @@ async function update(tagOrUrl = '') {
|
|
|
115
149
|
catch (_a) { }
|
|
116
150
|
(0, fs_1.renameSync)(bin, oldBin);
|
|
117
151
|
console.log("launching new version in background", newBinFile);
|
|
118
|
-
|
|
152
|
+
if (doingLocal)
|
|
153
|
+
try {
|
|
154
|
+
(0, fs_1.renameSync)(doingLocal, 'old-' + doingLocal);
|
|
155
|
+
}
|
|
156
|
+
catch (e) {
|
|
157
|
+
console.warn(e);
|
|
158
|
+
}
|
|
159
|
+
launch(newBin, ['--updating', binFile], { sync: true }); // sync necessary to work on Mac by double-click
|
|
119
160
|
});
|
|
120
161
|
console.log("quitting");
|
|
121
162
|
setTimeout(() => process.exit()); // give time to return (and caller to complete, eg: rest api to reply)
|
package/src/upload.js
CHANGED
|
@@ -61,9 +61,9 @@ function uploadWriter(base, path, ctx) {
|
|
|
61
61
|
else
|
|
62
62
|
try {
|
|
63
63
|
// refer to the source of the closest node that actually belongs to the vfs, so that cache is more effective
|
|
64
|
-
let closestVfsNode = base;
|
|
65
|
-
while (closestVfsNode && !closestVfsNode.original)
|
|
66
|
-
closestVfsNode = closestVfsNode.parent;
|
|
64
|
+
let closestVfsNode = base; // if base=root, there's no parent and no original
|
|
65
|
+
while ((closestVfsNode === null || closestVfsNode === void 0 ? void 0 : closestVfsNode.parent) && !closestVfsNode.original)
|
|
66
|
+
closestVfsNode = closestVfsNode.parent; // if it's not original, it surely has a parent
|
|
67
67
|
const statDir = closestVfsNode.source;
|
|
68
68
|
if (!Object.hasOwn(diskSpaceCache, statDir)) {
|
|
69
69
|
const c = diskSpaceCache[statDir] = (0, util_os_1.getDiskSpaceSync)(statDir);
|
|
@@ -86,14 +86,15 @@ function uploadWriter(base, path, ctx) {
|
|
|
86
86
|
if (ctx.query.existing === 'skip' && fs_1.default.existsSync(fullPath))
|
|
87
87
|
return fail(const_1.HTTP_CONFLICT, 'exists');
|
|
88
88
|
openFiles.add(fullPath);
|
|
89
|
+
let overwriteRequestedButForbidden = false;
|
|
89
90
|
try {
|
|
90
91
|
// if upload creates a folder, then add meta to it too
|
|
91
|
-
if (fs_1.default.mkdirSync(dir, { recursive: true }))
|
|
92
|
+
if (!dir.endsWith(':\\') && fs_1.default.mkdirSync(dir, { recursive: true }))
|
|
92
93
|
setUploadMeta(dir, ctx);
|
|
93
94
|
// use temporary name while uploading
|
|
94
95
|
const keepName = (0, path_1.basename)(fullPath).slice(-200);
|
|
95
96
|
let tempName = (0, path_1.join)(dir, 'hfs$upload-' + keepName);
|
|
96
|
-
const resumable = fs_1.default.existsSync(tempName) && !openFiles.has(tempName) && tempName;
|
|
97
|
+
const resumable = fs_1.default.existsSync(tempName) && !openFiles.has(tempName) && tempName; // resumable is temp-file-1
|
|
97
98
|
if (resumable)
|
|
98
99
|
tempName = (0, path_1.join)(dir, 'hfs$upload2-' + keepName);
|
|
99
100
|
// checks for resume feature
|
|
@@ -101,15 +102,19 @@ function uploadWriter(base, path, ctx) {
|
|
|
101
102
|
const size = resumable && (0, misc_1.try_)(() => fs_1.default.statSync(resumable).size);
|
|
102
103
|
if (size === undefined) // stat failed
|
|
103
104
|
return fail(const_1.HTTP_SERVER_ERROR);
|
|
104
|
-
if (resume > size)
|
|
105
|
+
if (lodash_1.default.isNumber(size) && resume > size)
|
|
105
106
|
return fail(const_1.HTTP_RANGE_NOT_SATISFIABLE);
|
|
106
107
|
// warn frontend about resume possibility
|
|
108
|
+
let resumableLost = false;
|
|
107
109
|
if (!resume && resumable) {
|
|
108
110
|
const timeout = 30;
|
|
109
111
|
(0, frontEndApis_1.notifyClient)(ctx, 'upload.resumable', { [path]: size, expires: Date.now() + timeout * 1000 });
|
|
110
|
-
delayedDelete(resumable, timeout, () =>
|
|
111
|
-
|
|
112
|
-
|
|
112
|
+
delayedDelete(resumable, timeout, () => // if user resumes, this upload is interrupted, and next upload will cancel this delayedDelete
|
|
113
|
+
fs_1.default.rename(tempName, resumable, err => {
|
|
114
|
+
if (err)
|
|
115
|
+
return;
|
|
116
|
+
tempName = resumable;
|
|
117
|
+
resumableLost = true;
|
|
113
118
|
}));
|
|
114
119
|
}
|
|
115
120
|
// append if resuming
|
|
@@ -136,14 +141,21 @@ function uploadWriter(base, path, ctx) {
|
|
|
136
141
|
const lockMiddleware = (0, misc_1.pendingPromise)(); // outside we need to know when all operations stopped
|
|
137
142
|
writeStream.once('close', async () => {
|
|
138
143
|
try {
|
|
144
|
+
await new Promise(res => fileStream.close(res)); // this only seem to be necessary on Windows
|
|
139
145
|
if (ctx.req.aborted) {
|
|
140
|
-
if (resumable) // we don't want to be left with 2 temp files
|
|
141
|
-
return
|
|
146
|
+
if (resumable && !resumableLost && !resuming) // we don't want to be left with 2 temp files
|
|
147
|
+
return (0, promises_1.rm)(tempName);
|
|
142
148
|
const sec = exports.deleteUnfinishedUploadsAfter.get();
|
|
143
149
|
return lodash_1.default.isNumber(sec) && delayedDelete(tempName, sec);
|
|
144
150
|
}
|
|
151
|
+
if (ctx.query.partial)
|
|
152
|
+
return; // this upload is partial, and we are supposed to leave the upload as unfinished, with the temp name
|
|
145
153
|
let dest = fullPath;
|
|
146
154
|
if (exports.dontOverwriteUploading.get() && !await overwriteAnyway() && fs_1.default.existsSync(dest)) {
|
|
155
|
+
if (overwriteRequestedButForbidden) {
|
|
156
|
+
await (0, promises_1.rm)(tempName);
|
|
157
|
+
return fail();
|
|
158
|
+
}
|
|
147
159
|
const ext = (0, path_1.extname)(dest);
|
|
148
160
|
const base = dest.slice(0, -ext.length || Infinity);
|
|
149
161
|
let i = 1;
|
|
@@ -153,12 +165,13 @@ function uploadWriter(base, path, ctx) {
|
|
|
153
165
|
}
|
|
154
166
|
try {
|
|
155
167
|
await (0, promises_1.rename)(tempName, dest);
|
|
168
|
+
cancelDeletion(tempName); // not necessary, as deletion's failure is silent, but still
|
|
156
169
|
ctx.state.uploadDestinationPath = dest;
|
|
157
170
|
setUploadMeta(dest, ctx);
|
|
158
171
|
if (ctx.query.comment)
|
|
159
172
|
void (0, comments_1.setCommentFor)(dest, String(ctx.query.comment));
|
|
160
|
-
if (resumable)
|
|
161
|
-
|
|
173
|
+
if (resumable && !resuming) // this happens if user decided to not resume and the new upload finished before delayedDelete
|
|
174
|
+
(0, promises_1.rm)(resumable).catch(console.warn);
|
|
162
175
|
events_1.default.emit('uploadFinished', obj);
|
|
163
176
|
if (resEvent)
|
|
164
177
|
for (const cb of resEvent)
|
|
@@ -182,7 +195,7 @@ function uploadWriter(base, path, ctx) {
|
|
|
182
195
|
let lastGot = 0;
|
|
183
196
|
let lastGotTime = 0;
|
|
184
197
|
const opTotal = reqSize + resume;
|
|
185
|
-
Object.assign(ctx.state, {
|
|
198
|
+
Object.assign(ctx.state, { opTotal, opOffset: resume / opTotal, opProgress: 0 });
|
|
186
199
|
const conn = (0, connections_1.updateConnectionForCtx)(ctx);
|
|
187
200
|
if (!conn)
|
|
188
201
|
return;
|
|
@@ -202,11 +215,13 @@ function uploadWriter(base, path, ctx) {
|
|
|
202
215
|
throw e;
|
|
203
216
|
}
|
|
204
217
|
async function overwriteAnyway() {
|
|
205
|
-
if (ctx.query.
|
|
206
|
-
|
|
207
|
-
return;
|
|
218
|
+
if (ctx.query.existing !== 'overwrite')
|
|
219
|
+
return false;
|
|
208
220
|
const n = await (0, vfs_1.getNodeByName)(path, base);
|
|
209
|
-
|
|
221
|
+
if (n && !(0, vfs_1.statusCodeForMissingPerm)(n, 'can_delete', ctx))
|
|
222
|
+
return true;
|
|
223
|
+
overwriteRequestedButForbidden = true;
|
|
224
|
+
return false;
|
|
210
225
|
}
|
|
211
226
|
function delayedDelete(path, secs, cb) {
|
|
212
227
|
clearTimeout(waitingToBeDeleted[path]);
|