hfs 3.0.7 → 3.1.0-alpha2
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 +1 -1
- package/admin/assets/index-CDiYkO8j.css +1 -0
- package/admin/assets/index-Dm5BN1iZ.js +1 -0
- package/admin/assets/index-P5i8LyWl.js +822 -0
- package/admin/assets/{sha512-gDcjPgJS.js → sha512-DG-ElrDe.js} +1 -1
- package/admin/flags/freakflags.css +284 -0
- package/admin/flags/sprite.png +0 -0
- package/admin/index.html +3 -2
- package/frontend/assets/index-legacy-CG_og8xL.js +1 -0
- package/frontend/assets/index-legacy-DDLKWGcm.js +9 -0
- package/frontend/assets/{sha512-legacy-BBwwadDl.js → sha512-legacy-D1zEZzDe.js} +1 -1
- package/frontend/fontello.css +3 -1
- package/frontend/fontello.woff2 +0 -0
- package/frontend/index.html +1 -1
- package/npm-shrinkwrap.json +16409 -0
- package/package.json +12 -11
- package/plugins/antibrute/plugin.js +14 -3
- package/src/AsapStream.js +49 -0
- package/src/ThrottledStream.js +1 -1
- package/src/adminApis.js +16 -5
- package/src/api.accounts.js +1 -6
- package/src/api.auth.js +103 -100
- package/src/api.get_file_list.js +7 -0
- package/src/api.lang.js +1 -0
- package/src/api.log.js +5 -3
- package/src/api.monitor.js +5 -0
- package/src/api.net.js +5 -0
- package/src/api.plugins.js +13 -2
- package/src/api.vfs.js +41 -28
- package/src/apiMiddleware.js +5 -4
- package/src/auth.js +39 -6
- package/src/commands.js +16 -27
- package/src/comments.js +4 -4
- package/src/config.js +5 -2
- package/src/const.js +2 -16
- package/src/cross-const.js +5 -2
- package/src/cross.js +21 -15
- package/src/expiringCache.js +16 -5
- package/src/fileAttr.js +1 -1
- package/src/first.js +7 -4
- package/src/frontEndApis.js +88 -102
- package/src/github.js +32 -21
- package/src/ips.js +1 -1
- package/src/log.js +5 -3
- package/src/middlewares.js +2 -1
- package/src/misc.js +2 -20
- package/src/perm.js +27 -12
- package/src/persistence.js +1 -1
- package/src/plugins.js +12 -6
- package/src/serveFile.js +3 -3
- package/src/serveGuiAndSharedFiles.js +3 -0
- package/src/serveGuiFiles.js +2 -1
- package/src/srp.js +5 -6
- package/src/stat.js +4 -1
- package/src/update.js +18 -25
- package/src/upload.js +4 -3
- package/src/util-files.js +2 -1
- package/src/util-http.js +3 -3
- package/src/vfs.js +4 -2
- package/src/webdav.js +297 -0
- package/admin/assets/index-Bj9Dk3JN.js +0 -822
- package/admin/assets/index-Byv1_NxP.css +0 -1
- package/admin/flags/ad.png +0 -0
- package/admin/flags/ae.png +0 -0
- package/admin/flags/af.png +0 -0
- package/admin/flags/ag.png +0 -0
- package/admin/flags/ai.png +0 -0
- package/admin/flags/al.png +0 -0
- package/admin/flags/am.png +0 -0
- package/admin/flags/ao.png +0 -0
- package/admin/flags/aq.png +0 -0
- package/admin/flags/ar.png +0 -0
- package/admin/flags/as.png +0 -0
- package/admin/flags/at.png +0 -0
- package/admin/flags/au.png +0 -0
- package/admin/flags/aw.png +0 -0
- package/admin/flags/ax.png +0 -0
- package/admin/flags/az.png +0 -0
- package/admin/flags/ba.png +0 -0
- package/admin/flags/bb.png +0 -0
- package/admin/flags/bd.png +0 -0
- package/admin/flags/be.png +0 -0
- package/admin/flags/bf.png +0 -0
- package/admin/flags/bg.png +0 -0
- package/admin/flags/bh.png +0 -0
- package/admin/flags/bi.png +0 -0
- package/admin/flags/bj.png +0 -0
- package/admin/flags/bl.png +0 -0
- package/admin/flags/bm.png +0 -0
- package/admin/flags/bn.png +0 -0
- package/admin/flags/bo.png +0 -0
- package/admin/flags/bq.png +0 -0
- package/admin/flags/br.png +0 -0
- package/admin/flags/bs.png +0 -0
- package/admin/flags/bt.png +0 -0
- package/admin/flags/bv.png +0 -0
- package/admin/flags/bw.png +0 -0
- package/admin/flags/by.png +0 -0
- package/admin/flags/bz.png +0 -0
- package/admin/flags/ca.png +0 -0
- package/admin/flags/cc.png +0 -0
- package/admin/flags/cd.png +0 -0
- package/admin/flags/cf.png +0 -0
- package/admin/flags/cg.png +0 -0
- package/admin/flags/ch.png +0 -0
- package/admin/flags/ci.png +0 -0
- package/admin/flags/ck.png +0 -0
- package/admin/flags/cl.png +0 -0
- package/admin/flags/cm.png +0 -0
- package/admin/flags/cn.png +0 -0
- package/admin/flags/co.png +0 -0
- package/admin/flags/cr.png +0 -0
- package/admin/flags/cu.png +0 -0
- package/admin/flags/cv.png +0 -0
- package/admin/flags/cw.png +0 -0
- package/admin/flags/cx.png +0 -0
- package/admin/flags/cy.png +0 -0
- package/admin/flags/cz.png +0 -0
- package/admin/flags/de.png +0 -0
- package/admin/flags/dj.png +0 -0
- package/admin/flags/dk.png +0 -0
- package/admin/flags/dm.png +0 -0
- package/admin/flags/do.png +0 -0
- package/admin/flags/dz.png +0 -0
- package/admin/flags/ec.png +0 -0
- package/admin/flags/ee.png +0 -0
- package/admin/flags/eg.png +0 -0
- package/admin/flags/eh.png +0 -0
- package/admin/flags/er.png +0 -0
- package/admin/flags/es.png +0 -0
- package/admin/flags/et.png +0 -0
- package/admin/flags/fi.png +0 -0
- package/admin/flags/fj.png +0 -0
- package/admin/flags/fk.png +0 -0
- package/admin/flags/fm.png +0 -0
- package/admin/flags/fo.png +0 -0
- package/admin/flags/fr.png +0 -0
- package/admin/flags/ga.png +0 -0
- package/admin/flags/gb-eng.png +0 -0
- package/admin/flags/gb-nir.png +0 -0
- package/admin/flags/gb-sct.png +0 -0
- package/admin/flags/gb-wls.png +0 -0
- package/admin/flags/gb.png +0 -0
- package/admin/flags/gd.png +0 -0
- package/admin/flags/ge.png +0 -0
- package/admin/flags/gf.png +0 -0
- package/admin/flags/gg.png +0 -0
- package/admin/flags/gh.png +0 -0
- package/admin/flags/gi.png +0 -0
- package/admin/flags/gl.png +0 -0
- package/admin/flags/gm.png +0 -0
- package/admin/flags/gn.png +0 -0
- package/admin/flags/gp.png +0 -0
- package/admin/flags/gq.png +0 -0
- package/admin/flags/gr.png +0 -0
- package/admin/flags/gs.png +0 -0
- package/admin/flags/gt.png +0 -0
- package/admin/flags/gu.png +0 -0
- package/admin/flags/gw.png +0 -0
- package/admin/flags/gy.png +0 -0
- package/admin/flags/hk.png +0 -0
- package/admin/flags/hm.png +0 -0
- package/admin/flags/hn.png +0 -0
- package/admin/flags/hr.png +0 -0
- package/admin/flags/ht.png +0 -0
- package/admin/flags/hu.png +0 -0
- package/admin/flags/id.png +0 -0
- package/admin/flags/ie.png +0 -0
- package/admin/flags/il.png +0 -0
- package/admin/flags/im.png +0 -0
- package/admin/flags/in.png +0 -0
- package/admin/flags/io.png +0 -0
- package/admin/flags/iq.png +0 -0
- package/admin/flags/ir.png +0 -0
- package/admin/flags/is.png +0 -0
- package/admin/flags/it.png +0 -0
- package/admin/flags/je.png +0 -0
- package/admin/flags/jm.png +0 -0
- package/admin/flags/jo.png +0 -0
- package/admin/flags/jp.png +0 -0
- package/admin/flags/ke.png +0 -0
- package/admin/flags/kg.png +0 -0
- package/admin/flags/kh.png +0 -0
- package/admin/flags/ki.png +0 -0
- package/admin/flags/km.png +0 -0
- package/admin/flags/kn.png +0 -0
- package/admin/flags/kp.png +0 -0
- package/admin/flags/kr.png +0 -0
- package/admin/flags/kw.png +0 -0
- package/admin/flags/ky.png +0 -0
- package/admin/flags/kz.png +0 -0
- package/admin/flags/la.png +0 -0
- package/admin/flags/lb.png +0 -0
- package/admin/flags/lc.png +0 -0
- package/admin/flags/li.png +0 -0
- package/admin/flags/lk.png +0 -0
- package/admin/flags/lr.png +0 -0
- package/admin/flags/ls.png +0 -0
- package/admin/flags/lt.png +0 -0
- package/admin/flags/lu.png +0 -0
- package/admin/flags/lv.png +0 -0
- package/admin/flags/ly.png +0 -0
- package/admin/flags/ma.png +0 -0
- package/admin/flags/mc.png +0 -0
- package/admin/flags/md.png +0 -0
- package/admin/flags/me.png +0 -0
- package/admin/flags/mf.png +0 -0
- package/admin/flags/mg.png +0 -0
- package/admin/flags/mh.png +0 -0
- package/admin/flags/mk.png +0 -0
- package/admin/flags/ml.png +0 -0
- package/admin/flags/mm.png +0 -0
- package/admin/flags/mn.png +0 -0
- package/admin/flags/mo.png +0 -0
- package/admin/flags/mp.png +0 -0
- package/admin/flags/mq.png +0 -0
- package/admin/flags/mr.png +0 -0
- package/admin/flags/ms.png +0 -0
- package/admin/flags/mt.png +0 -0
- package/admin/flags/mu.png +0 -0
- package/admin/flags/mv.png +0 -0
- package/admin/flags/mw.png +0 -0
- package/admin/flags/mx.png +0 -0
- package/admin/flags/my.png +0 -0
- package/admin/flags/mz.png +0 -0
- package/admin/flags/na.png +0 -0
- package/admin/flags/nc.png +0 -0
- package/admin/flags/ne.png +0 -0
- package/admin/flags/nf.png +0 -0
- package/admin/flags/ng.png +0 -0
- package/admin/flags/ni.png +0 -0
- package/admin/flags/nl.png +0 -0
- package/admin/flags/no.png +0 -0
- package/admin/flags/np.png +0 -0
- package/admin/flags/nr.png +0 -0
- package/admin/flags/nu.png +0 -0
- package/admin/flags/nz.png +0 -0
- package/admin/flags/om.png +0 -0
- package/admin/flags/pa.png +0 -0
- package/admin/flags/pe.png +0 -0
- package/admin/flags/pf.png +0 -0
- package/admin/flags/pg.png +0 -0
- package/admin/flags/ph.png +0 -0
- package/admin/flags/pk.png +0 -0
- package/admin/flags/pl.png +0 -0
- package/admin/flags/pm.png +0 -0
- package/admin/flags/pn.png +0 -0
- package/admin/flags/pr.png +0 -0
- package/admin/flags/ps.png +0 -0
- package/admin/flags/pt.png +0 -0
- package/admin/flags/pw.png +0 -0
- package/admin/flags/py.png +0 -0
- package/admin/flags/qa.png +0 -0
- package/admin/flags/re.png +0 -0
- package/admin/flags/ro.png +0 -0
- package/admin/flags/rs.png +0 -0
- package/admin/flags/ru.png +0 -0
- package/admin/flags/rw.png +0 -0
- package/admin/flags/sa.png +0 -0
- package/admin/flags/sb.png +0 -0
- package/admin/flags/sc.png +0 -0
- package/admin/flags/sd.png +0 -0
- package/admin/flags/se.png +0 -0
- package/admin/flags/sg.png +0 -0
- package/admin/flags/sh.png +0 -0
- package/admin/flags/si.png +0 -0
- package/admin/flags/sj.png +0 -0
- package/admin/flags/sk.png +0 -0
- package/admin/flags/sl.png +0 -0
- package/admin/flags/sm.png +0 -0
- package/admin/flags/sn.png +0 -0
- package/admin/flags/so.png +0 -0
- package/admin/flags/sr.png +0 -0
- package/admin/flags/ss.png +0 -0
- package/admin/flags/st.png +0 -0
- package/admin/flags/sv.png +0 -0
- package/admin/flags/sx.png +0 -0
- package/admin/flags/sy.png +0 -0
- package/admin/flags/sz.png +0 -0
- package/admin/flags/tc.png +0 -0
- package/admin/flags/td.png +0 -0
- package/admin/flags/tf.png +0 -0
- package/admin/flags/tg.png +0 -0
- package/admin/flags/th.png +0 -0
- package/admin/flags/tj.png +0 -0
- package/admin/flags/tk.png +0 -0
- package/admin/flags/tl.png +0 -0
- package/admin/flags/tm.png +0 -0
- package/admin/flags/tn.png +0 -0
- package/admin/flags/to.png +0 -0
- package/admin/flags/tr.png +0 -0
- package/admin/flags/tt.png +0 -0
- package/admin/flags/tv.png +0 -0
- package/admin/flags/tw.png +0 -0
- package/admin/flags/tz.png +0 -0
- package/admin/flags/ua.png +0 -0
- package/admin/flags/ug.png +0 -0
- package/admin/flags/um.png +0 -0
- package/admin/flags/us.png +0 -0
- package/admin/flags/uy.png +0 -0
- package/admin/flags/uz.png +0 -0
- package/admin/flags/va.png +0 -0
- package/admin/flags/vc.png +0 -0
- package/admin/flags/ve.png +0 -0
- package/admin/flags/vg.png +0 -0
- package/admin/flags/vi.png +0 -0
- package/admin/flags/vn.png +0 -0
- package/admin/flags/vu.png +0 -0
- package/admin/flags/wf.png +0 -0
- package/admin/flags/ws.png +0 -0
- package/admin/flags/xk.png +0 -0
- package/admin/flags/ye.png +0 -0
- package/admin/flags/yt.png +0 -0
- package/admin/flags/za.png +0 -0
- package/admin/flags/zm.png +0 -0
- package/admin/flags/zw.png +0 -0
- package/frontend/assets/index-legacy-COBT1YmS.js +0 -50
package/src/srp.js
CHANGED
|
@@ -3,19 +3,18 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
4
|
exports.srpClientSequence = srpClientSequence;
|
|
5
5
|
exports.srpClientPart = srpClientPart;
|
|
6
|
-
|
|
7
|
-
async function srpClientSequence(username, password, apiCall, extra) {
|
|
6
|
+
async function srpClientSequence(srp, username, password, apiCall, extra) {
|
|
8
7
|
const { pubKey, salt } = await apiCall('loginSrp1', { username });
|
|
9
8
|
if (!salt)
|
|
10
9
|
throw Error('salt');
|
|
11
|
-
const client = await srpClientPart(username, password, salt, pubKey);
|
|
10
|
+
const client = await srpClientPart(srp, username, password, salt, pubKey);
|
|
12
11
|
const res = await apiCall('loginSrp2', { pubKey: String(client.A), proof: String(client.M1), ...extra }); // bigint-s must be cast to string to be json-ed
|
|
13
12
|
await client.step3(BigInt(res.proof)).catch(() => Promise.reject('trust'));
|
|
14
13
|
return res;
|
|
15
14
|
}
|
|
16
|
-
async function srpClientPart(username, password, salt, pubKey) {
|
|
17
|
-
const srp6aNimbusRoutines = new
|
|
18
|
-
const srpClient = new
|
|
15
|
+
async function srpClientPart(srp, username, password, salt, pubKey) {
|
|
16
|
+
const srp6aNimbusRoutines = new srp.SRPRoutines(new srp.SRPParameters());
|
|
17
|
+
const srpClient = new srp.SRPClientSession(srp6aNimbusRoutines);
|
|
19
18
|
const res = await srpClient.step1(username, password);
|
|
20
19
|
return await res.step2(BigInt(salt), BigInt(pubKey));
|
|
21
20
|
}
|
package/src/stat.js
CHANGED
|
@@ -4,9 +4,12 @@ exports.getStatWorker = getStatWorker;
|
|
|
4
4
|
const node_worker_threads_1 = require("node:worker_threads");
|
|
5
5
|
const node_fs_1 = require("node:fs");
|
|
6
6
|
const cross_1 = require("./cross");
|
|
7
|
+
const first_1 = require("./first");
|
|
7
8
|
// all stat requests for the same worker are serialized, potentially introducing extra latency
|
|
8
9
|
const pool = new Map();
|
|
9
10
|
function getStatWorker(key) {
|
|
11
|
+
if (first_1.quitting)
|
|
12
|
+
return () => Promise.reject('quitting');
|
|
10
13
|
const existing = pool.get(key);
|
|
11
14
|
if (existing)
|
|
12
15
|
return existing;
|
|
@@ -15,7 +18,7 @@ function getStatWorker(key) {
|
|
|
15
18
|
const requests = new Map();
|
|
16
19
|
worker.on('message', (msg) => {
|
|
17
20
|
const k = msg.path;
|
|
18
|
-
requests.get(k)?.resolve(msg.error ? Promise.reject(
|
|
21
|
+
requests.get(k)?.resolve(msg.error ? Promise.reject(Error(msg.error))
|
|
19
22
|
: Object.setPrototypeOf(msg.result, node_fs_1.Stats.prototype));
|
|
20
23
|
requests.delete(k);
|
|
21
24
|
});
|
package/src/update.js
CHANGED
|
@@ -19,7 +19,6 @@ const misc_1 = require("./misc");
|
|
|
19
19
|
const fs_1 = require("fs");
|
|
20
20
|
const plugins_1 = require("./plugins");
|
|
21
21
|
const promises_1 = require("fs/promises");
|
|
22
|
-
const open_1 = __importDefault(require("open"));
|
|
23
22
|
const config_1 = require("./config");
|
|
24
23
|
const util_os_1 = require("./util-os");
|
|
25
24
|
const first_1 = require("./first");
|
|
@@ -134,14 +133,18 @@ async function update(tagOrUrl = '') {
|
|
|
134
133
|
}
|
|
135
134
|
if (url) {
|
|
136
135
|
console.log("downloading", url);
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
136
|
+
try {
|
|
137
|
+
await (0, promises_1.writeFile)(LOCAL_UPDATE, await (0, misc_1.httpStream)(url));
|
|
138
|
+
}
|
|
139
|
+
catch (e) {
|
|
140
|
+
await (0, promises_1.rm)(LOCAL_UPDATE).catch(() => { }); // no leftovers
|
|
141
|
+
throw "Download failed for " + url + (0, misc_1.prefix)(' – ', e?.message);
|
|
142
|
+
}
|
|
143
|
+
console.debug("download finished");
|
|
141
144
|
}
|
|
142
145
|
const bin = process.execPath;
|
|
143
146
|
const binPath = (0, path_1.dirname)(bin);
|
|
144
|
-
const binFile = 'hfs' + (const_1.IS_WINDOWS ? '.exe' : ''); // currently running
|
|
147
|
+
const binFile = 'hfs' + (const_1.IS_WINDOWS ? '.exe' : ''); // the bin we are currently running could have been renamed
|
|
145
148
|
let newBinFile = binFile;
|
|
146
149
|
do {
|
|
147
150
|
newBinFile = 'new-' + newBinFile;
|
|
@@ -170,7 +173,7 @@ async function update(tagOrUrl = '') {
|
|
|
170
173
|
catch { }
|
|
171
174
|
(0, fs_1.renameSync)(bin, oldBin);
|
|
172
175
|
console.log("launching new version in background", newBinFile);
|
|
173
|
-
|
|
176
|
+
(0, child_process_1.spawnSync)((0, util_os_1.cmdEscape)(newBin), ['--updating', binFile, '--cwd .'], { shell: true, stdio: [0, 1, 2] }); // sync necessary to work on Mac by double-click
|
|
174
177
|
});
|
|
175
178
|
console.log("quitting");
|
|
176
179
|
setTimeout(() => process.exit()); // give time to return (and caller to complete, eg: rest api to reply)
|
|
@@ -180,28 +183,18 @@ async function update(tagOrUrl = '') {
|
|
|
180
183
|
throw e?.message || String(e);
|
|
181
184
|
}
|
|
182
185
|
}
|
|
183
|
-
function launch(cmd, pars = [], options) {
|
|
184
|
-
return (options?.sync ? child_process_1.spawnSync : child_process_1.spawn)((0, util_os_1.cmdEscape)(cmd), pars, { detached: true, shell: true, stdio: [0, 1, 2], ...options });
|
|
185
|
-
}
|
|
186
186
|
if (argv_1.argv.updating) { // we were launched with a temporary name, restore original name to avoid breaking references
|
|
187
187
|
const bin = process.execPath;
|
|
188
188
|
const dest = (0, path_1.join)((0, path_1.dirname)(bin), argv_1.argv.updating);
|
|
189
189
|
(0, fs_1.renameSync)(bin, dest);
|
|
190
|
-
// have to relaunch with the new name, or otherwise next update will fail with EBUSY on hfs.exe
|
|
190
|
+
// have to relaunch with the new name, or otherwise the next update will fail with EBUSY on hfs.exe
|
|
191
191
|
console.log(`renamed binary file to "${argv_1.argv.updating}" and now restarting`);
|
|
192
|
-
// be sure to test launching both double-clicking and in a terminal
|
|
193
|
-
if (const_1.IS_WINDOWS) // this method on Mac works only once, and without console
|
|
194
|
-
(0, first_1.onProcessExit)(() =>
|
|
195
|
-
else
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
*/
|
|
200
|
-
try {
|
|
201
|
-
(0, fs_1.writeFileSync)(const_1.ARGS_FILE, JSON.stringify(['--updated', '--cwd', process.cwd().replaceAll(' ', '\\ ')]));
|
|
202
|
-
}
|
|
203
|
-
catch { }
|
|
204
|
-
void (0, open_1.default)(dest);
|
|
205
|
-
}
|
|
192
|
+
// if you change anything, be sure to test launching both double-clicking and in a terminal
|
|
193
|
+
if (const_1.IS_WINDOWS) // windows-only; this method on Mac works only once, and without the console
|
|
194
|
+
(0, first_1.onProcessExit)(() => (0, child_process_1.spawn)((0, util_os_1.cmdEscape)(dest), ['--updated', '--cwd .'], { detached: true, shell: true, stdio: [0, 1, 2] })); // launch+sync here would cause the old process to stay open, locking ports
|
|
195
|
+
else if (process.stdin.isTTY && process.stdout.isTTY) // keep interactive terminal users attached to the restarted process
|
|
196
|
+
(0, child_process_1.spawnSync)(dest, ['--updated', '--cwd', process.cwd()], { stdio: [0, 1, 2] });
|
|
197
|
+
else
|
|
198
|
+
(0, child_process_1.spawn)(dest, ['--updated', '--cwd', process.cwd()], { detached: true, stdio: 'ignore' }).unref();
|
|
206
199
|
process.exit();
|
|
207
200
|
}
|
package/src/upload.js
CHANGED
|
@@ -54,7 +54,7 @@ const diskSpaceCache = (0, expiringCache_1.expiringCache)(3_000); // invalidate
|
|
|
54
54
|
const uploadingFiles = new Map();
|
|
55
55
|
// initially sync for formidable; still sync to avoid async races and PUT piping gaps
|
|
56
56
|
function uploadWriter(base, baseUri, filename, ctx) {
|
|
57
|
-
if (!filename || !(0, misc_1.isValidFileName)(filename))
|
|
57
|
+
if (!filename || !(0, misc_1.isValidFileName)(filename) || !filename)
|
|
58
58
|
return fail(const_1.HTTP_FOOL);
|
|
59
59
|
if ((0, vfs_1.statusCodeForMissingPerm)(base, 'can_upload', ctx))
|
|
60
60
|
return fail();
|
|
@@ -133,10 +133,10 @@ function uploadWriter(base, baseUri, filename, ctx) {
|
|
|
133
133
|
fs_1.default.unlinkSync(tempName);
|
|
134
134
|
const writeStream = (0, misc_1.createStreamLimiter)(contentLength ?? Infinity);
|
|
135
135
|
const fullSize = stillToWrite + resume;
|
|
136
|
-
ctx.state.uploadDestinationPath = tempName;
|
|
137
136
|
// allow plugins to mess with the write-stream, because the read-stream can be complicated in case of multipart
|
|
138
137
|
const obj = { ctx, writeStream, fullPath, tempName, resume, fullSize, uri: '' };
|
|
139
138
|
const resEvent = events_1.default.emit('uploadStart', obj);
|
|
139
|
+
ctx.state.uploadDestinationPath = tempName;
|
|
140
140
|
if (resEvent?.isDefaultPrevented())
|
|
141
141
|
return;
|
|
142
142
|
const fileStream = fs_1.default.createWriteStream(tempName, resume ? { flags: 'r+', start: resume } : undefined);
|
|
@@ -282,7 +282,8 @@ function uploadWriter(base, baseUri, filename, ctx) {
|
|
|
282
282
|
if (msg)
|
|
283
283
|
ctx.body = msg;
|
|
284
284
|
if (status >= 400 // with other codes Chrome will report ERR_CONNECTION_RESET
|
|
285
|
-
&& !ctx.get('x-hfs-wait')
|
|
285
|
+
&& !ctx.get('x-hfs-wait') // you can disable the following behavior
|
|
286
|
+
&& !ctx.req.complete) // if request body is already complete, forcing a disconnect can interfere with follow-up requests on reused sockets.
|
|
286
287
|
setTimeout(() => (0, connections_1.disconnect)(ctx), 200); // don't wait, if the upload is still in progress
|
|
287
288
|
}
|
|
288
289
|
}
|
package/src/util-files.js
CHANGED
|
@@ -6,6 +6,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
6
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
7
|
exports.parseFileCache = void 0;
|
|
8
8
|
exports.statWithTimeout = statWithTimeout;
|
|
9
|
+
exports.getUncHost = getUncHost;
|
|
9
10
|
exports.isDirectory = isDirectory;
|
|
10
11
|
exports.readFileWithBusyRetry = readFileWithBusyRetry;
|
|
11
12
|
exports.watchDir = watchDir;
|
|
@@ -163,7 +164,7 @@ async function createSafeWriteStream(path, options) {
|
|
|
163
164
|
});
|
|
164
165
|
}
|
|
165
166
|
function isValidFileName(name, acceptUnreadable = false) {
|
|
166
|
-
return name !== '.' && !(const_1.IS_WINDOWS ? /[/:"*?<>|\\]/ : /\//).test(name) && !hasDirTraversal(name)
|
|
167
|
+
return name && name !== '.' && !(const_1.IS_WINDOWS ? /[/:"*?<>|\\]/ : /\//).test(name) && !hasDirTraversal(name)
|
|
167
168
|
&& (acceptUnreadable || !/[\u0000-\u001F\u007F]/.test(name));
|
|
168
169
|
}
|
|
169
170
|
function exists(path) {
|
package/src/util-http.js
CHANGED
|
@@ -112,15 +112,15 @@ function httpStream(url, { body, proxy, jar, noRedirect, httpThrow = true, ...op
|
|
|
112
112
|
delete hostJar[k];
|
|
113
113
|
}
|
|
114
114
|
if (!res.statusCode || httpThrow && res.statusCode >= 400)
|
|
115
|
-
return reject(
|
|
115
|
+
return reject(Error(String(res.statusCode), { cause: res }));
|
|
116
116
|
let r = res.headers.location;
|
|
117
117
|
if (r && !noRedirect) {
|
|
118
118
|
const dest = new URL(r, url); // rewrite in case r is just a path, and thus relative to the current url
|
|
119
119
|
r = dest.toString();
|
|
120
120
|
const src = new URL(url);
|
|
121
121
|
const sameOrigin = src.protocol === dest.protocol && src.host === dest.host;
|
|
122
|
-
return redirected.includes(r) ? reject(
|
|
123
|
-
: redirected.length > 20 ? reject(
|
|
122
|
+
return redirected.includes(r) ? reject(Error('endless http redirection'))
|
|
123
|
+
: redirected.length > 20 ? reject(Error('excessive http redirection'))
|
|
124
124
|
: resolve(httpStream(r, {
|
|
125
125
|
httpThrow, jar, proxy,
|
|
126
126
|
...lodash_1.default.pick(options, ['agent', 'rejectUnauthorized', 'timeout']),
|
package/src/vfs.js
CHANGED
|
@@ -170,10 +170,12 @@ async function getNodeByName(name, parent, assumeMissingToBeFolder = false) {
|
|
|
170
170
|
return ret;
|
|
171
171
|
}
|
|
172
172
|
}
|
|
173
|
+
const smartUncFolderDetection = (0, config_1.defineConfig)('smart_unc_folder_detection', true);
|
|
173
174
|
async function setIsFolder(node) {
|
|
174
175
|
if (!node.source)
|
|
175
176
|
return;
|
|
176
|
-
const isFolder =
|
|
177
|
+
const isFolder = smartUncFolderDetection.get() && (0, misc_1.getUncHost)(node.source) ? !(0, path_1.basename)(node.source).includes('.') // no dot = folder – not very reliable but fast for unreachable unc hosts, and you can opt-out
|
|
178
|
+
: /[\\/]$/.test(node.source) || await nodeStats(node).then(x => x?.isDirectory(), () => undefined);
|
|
177
179
|
(0, misc_1.setHidden)(node, { isFolder });
|
|
178
180
|
return isFolder;
|
|
179
181
|
}
|
|
@@ -328,7 +330,7 @@ async function* walkNode(parent, { ctx, depth = Infinity, prefixPath = '', requi
|
|
|
328
330
|
stream.push(null);
|
|
329
331
|
return null;
|
|
330
332
|
}
|
|
331
|
-
if ((0, comments_1.usingDescriptIon)() && entry.name === comments_1.DESCRIPT_ION)
|
|
333
|
+
if ((0, comments_1.usingDescriptIon)() && (entry.name === comments_1.DESCRIPT_ION || entry.name === comments_1.DESCRIPT_ION_ALT))
|
|
332
334
|
return;
|
|
333
335
|
const { path } = entry; // this path is not the original deprecated property: we are overwriting/reusing it
|
|
334
336
|
const isFolder = entry.isDirectory();
|
package/src/webdav.js
ADDED
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.handledWebdav = handledWebdav;
|
|
4
|
+
const vfs_1 = require("./vfs");
|
|
5
|
+
const cross_1 = require("./cross");
|
|
6
|
+
const stream_1 = require("stream");
|
|
7
|
+
const promises_1 = require("fs/promises");
|
|
8
|
+
const misc_1 = require("./misc");
|
|
9
|
+
const path_1 = require("path");
|
|
10
|
+
const frontEndApis_1 = require("./frontEndApis");
|
|
11
|
+
const node_crypto_1 = require("node:crypto");
|
|
12
|
+
const const_1 = require("./const");
|
|
13
|
+
const child_process_1 = require("child_process");
|
|
14
|
+
const auth_1 = require("./auth");
|
|
15
|
+
const config_1 = require("./config");
|
|
16
|
+
const expiringCache_1 = require("./expiringCache");
|
|
17
|
+
const forceWebdavLogin = (0, config_1.defineConfig)(cross_1.CFG.force_webdav_login, true, compileWebdavAgentRegex);
|
|
18
|
+
const webdavInitialAuth = (0, config_1.defineConfig)(cross_1.CFG.webdav_initial_auth, 'WebDAVFS', compileWebdavAgentRegex);
|
|
19
|
+
const webdavPrompted = (0, expiringCache_1.expiringCache)(cross_1.DAY);
|
|
20
|
+
const webdavDetectedAgents = new Set();
|
|
21
|
+
const TOKEN_HEADER = 'lock-token';
|
|
22
|
+
const WEBDAV_METHODS = new Set(['PROPFIND', 'PROPPATCH', 'MKCOL', 'MOVE', 'LOCK', 'UNLOCK']);
|
|
23
|
+
const WEBDAV_HINT_HEADERS = ['depth', 'destination', 'overwrite', 'translate', 'if', TOKEN_HEADER, 'x-expected-entity-length'];
|
|
24
|
+
const KNOWN_UA = /webdav|miniredir|davclnt/i;
|
|
25
|
+
const canOverwrite = new Set();
|
|
26
|
+
const locks = new Map();
|
|
27
|
+
function isLocked(path, ctx) {
|
|
28
|
+
const lock = locks.get(path);
|
|
29
|
+
if (!lock)
|
|
30
|
+
return false;
|
|
31
|
+
const ifHeader = ctx.get('If');
|
|
32
|
+
const tokenHeader = ctx.get(TOKEN_HEADER);
|
|
33
|
+
if (hasToken(ifHeader, lock.token) || hasToken(tokenHeader, lock.token))
|
|
34
|
+
return false;
|
|
35
|
+
ctx.status = cross_1.HTTP_LOCKED;
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
function hasToken(header, token) {
|
|
39
|
+
if (!header)
|
|
40
|
+
return false;
|
|
41
|
+
return header.includes(`<${token}>`) || header.split(/[,;\s]+/).includes(token);
|
|
42
|
+
}
|
|
43
|
+
async function handledWebdav(ctx) {
|
|
44
|
+
const { path } = ctx;
|
|
45
|
+
const isWebdavAuthRequest = WEBDAV_METHODS.has(ctx.method) || WEBDAV_HINT_HEADERS.some(h => !!ctx.get(h));
|
|
46
|
+
const ua = ctx.get('user-agent');
|
|
47
|
+
if (isWebdavAuthRequest && (0, auth_1.getCurrentUsername)(ctx)) {
|
|
48
|
+
if (ua)
|
|
49
|
+
webdavDetectedAgents.add(ua);
|
|
50
|
+
}
|
|
51
|
+
if (ctx.path.includes('/._') && ua?.startsWith('WebDAVFS')) { // too much spam from Finder for these files that can contain metas
|
|
52
|
+
ctx.state.dontLog = true;
|
|
53
|
+
return ctx.status = cross_1.HTTP_FORBIDDEN;
|
|
54
|
+
}
|
|
55
|
+
if (ctx.method === 'OPTIONS') {
|
|
56
|
+
if (ctx.get('Access-Control-Request-Method'))
|
|
57
|
+
return; // it's a preflight cors request, not webdav
|
|
58
|
+
setWebdavHeaders();
|
|
59
|
+
ctx.body = '';
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
if (isWebdavAuthRequest && shouldChallengeWebdav())
|
|
63
|
+
return true;
|
|
64
|
+
if (ctx.method === 'PUT') {
|
|
65
|
+
if (isLocked(path, ctx))
|
|
66
|
+
return true;
|
|
67
|
+
// Finder first creates an empty file (a test?) then wants to overwrite it, which requires deletion permission, but the user may not have it, causing a renamed upload. To solve, so we give it special permission for a few seconds.
|
|
68
|
+
const x = ctx.get('x-expected-entity-length'); // field used by Finder's webdav on actual upload, after
|
|
69
|
+
if (!x && !ctx.length) {
|
|
70
|
+
canOverwrite.add(path);
|
|
71
|
+
setTimeout(() => canOverwrite.delete(path), 10_000); // grace period
|
|
72
|
+
}
|
|
73
|
+
else if (canOverwrite.has(path)) {
|
|
74
|
+
canOverwrite.delete(path);
|
|
75
|
+
const node = await (0, vfs_1.urlToNode)(path, ctx);
|
|
76
|
+
if (node?.source)
|
|
77
|
+
await (0, promises_1.rm)(node.source).catch(() => { });
|
|
78
|
+
}
|
|
79
|
+
if (x && ctx.length === undefined) // missing length can make PUT fail
|
|
80
|
+
ctx.req.headers['content-length'] = x;
|
|
81
|
+
if (KNOWN_UA.test(ua) || webdavDetectedAgents.has(ua))
|
|
82
|
+
ctx.query.existing ??= 'overwrite'; // with webdav this is our default
|
|
83
|
+
return; // default handling
|
|
84
|
+
}
|
|
85
|
+
if (ctx.method === 'MKCOL') {
|
|
86
|
+
setWebdavHeaders();
|
|
87
|
+
if (isLocked(path, ctx))
|
|
88
|
+
return true;
|
|
89
|
+
const node = await (0, vfs_1.urlToNode)(path, ctx);
|
|
90
|
+
if (node)
|
|
91
|
+
return ctx.status = cross_1.HTTP_METHOD_NOT_ALLOWED;
|
|
92
|
+
let name = '';
|
|
93
|
+
const parentNode = await (0, vfs_1.urlToNode)(path, ctx, vfs_1.vfs, v => name = v);
|
|
94
|
+
if (!parentNode)
|
|
95
|
+
return ctx.status = cross_1.HTTP_NOT_FOUND;
|
|
96
|
+
if (!(0, misc_1.isValidFileName)(name))
|
|
97
|
+
return ctx.status = cross_1.HTTP_BAD_REQUEST;
|
|
98
|
+
if ((0, vfs_1.statusCodeForMissingPerm)(parentNode, 'can_upload', ctx)) {
|
|
99
|
+
if (ctx.status === cross_1.HTTP_UNAUTHORIZED)
|
|
100
|
+
setWebdavHeaders(true);
|
|
101
|
+
return true;
|
|
102
|
+
}
|
|
103
|
+
try {
|
|
104
|
+
await (0, promises_1.mkdir)((0, path_1.join)(parentNode.source, name));
|
|
105
|
+
return ctx.status = cross_1.HTTP_CREATED;
|
|
106
|
+
}
|
|
107
|
+
catch (e) {
|
|
108
|
+
return ctx.status = cross_1.HTTP_SERVER_ERROR;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
if (ctx.method === 'MOVE') {
|
|
112
|
+
setWebdavHeaders();
|
|
113
|
+
if (isLocked(path, ctx))
|
|
114
|
+
return true;
|
|
115
|
+
const node = await (0, vfs_1.urlToNode)(path, ctx);
|
|
116
|
+
if (!node)
|
|
117
|
+
return;
|
|
118
|
+
let dest = ctx.get('destination');
|
|
119
|
+
const i = dest.indexOf('//');
|
|
120
|
+
if (i >= 0)
|
|
121
|
+
dest = dest.slice(dest.indexOf('/', i + 2));
|
|
122
|
+
if (isLocked(dest, ctx))
|
|
123
|
+
return true;
|
|
124
|
+
if ((0, path_1.dirname)(path) === (0, path_1.dirname)(dest)) // rename. `path` is is encoded, so we test before decoding `dest`
|
|
125
|
+
try {
|
|
126
|
+
await (0, frontEndApis_1.requestedRename)(node, (0, path_1.basename)(decodeURI(dest)), ctx);
|
|
127
|
+
return ctx.status = cross_1.HTTP_CREATED;
|
|
128
|
+
}
|
|
129
|
+
catch (e) {
|
|
130
|
+
return ctx.status = e.status || cross_1.HTTP_SERVER_ERROR;
|
|
131
|
+
}
|
|
132
|
+
const moveRes = await (0, frontEndApis_1.moveFiles)([path], (0, path_1.dirname)(dest), ctx);
|
|
133
|
+
if (moveRes instanceof Error)
|
|
134
|
+
return ctx.status = moveRes.status || cross_1.HTTP_SERVER_ERROR;
|
|
135
|
+
const err = moveRes?.errors?.[0];
|
|
136
|
+
return ctx.status = !err ? cross_1.HTTP_CREATED : typeof err === 'number' ? err : cross_1.HTTP_SERVER_ERROR;
|
|
137
|
+
}
|
|
138
|
+
if (ctx.method === 'DELETE') {
|
|
139
|
+
setWebdavHeaders();
|
|
140
|
+
if (isLocked(path, ctx))
|
|
141
|
+
return true;
|
|
142
|
+
return; // allow default handling in serveGuiAndSharedFiles.ts
|
|
143
|
+
}
|
|
144
|
+
if (ctx.method === 'UNLOCK') {
|
|
145
|
+
setWebdavHeaders();
|
|
146
|
+
const x = ctx.get(TOKEN_HEADER).slice(1, -1);
|
|
147
|
+
const lock = locks.get(path);
|
|
148
|
+
if (x !== lock?.token)
|
|
149
|
+
return ctx.status = cross_1.HTTP_BAD_REQUEST;
|
|
150
|
+
clearTimeout(lock.timeout);
|
|
151
|
+
locks.delete(path);
|
|
152
|
+
ctx.set(TOKEN_HEADER, x);
|
|
153
|
+
if (const_1.IS_MAC)
|
|
154
|
+
(0, vfs_1.urlToNode)(path, ctx).then(x => x?.source && dotClean((0, path_1.dirname)(x.source)));
|
|
155
|
+
return ctx.status = cross_1.HTTP_NO_CONTENT;
|
|
156
|
+
}
|
|
157
|
+
if (ctx.method === 'LOCK') {
|
|
158
|
+
setWebdavHeaders();
|
|
159
|
+
if (locks.has(path))
|
|
160
|
+
return ctx.status = 423;
|
|
161
|
+
const token = 'urn:uuid:' + (0, node_crypto_1.randomUUID)();
|
|
162
|
+
ctx.set(TOKEN_HEADER, token);
|
|
163
|
+
const seconds = 3600;
|
|
164
|
+
const timeout = setTimeout(() => locks.delete(path), seconds * 1000);
|
|
165
|
+
locks.set(path, { token, timeout });
|
|
166
|
+
ctx.body = `<?xml version="1.0" encoding="utf-8"?><prop xmlns="DAV:"><lockdiscovery><activelock>
|
|
167
|
+
<locktype><write/></locktype>
|
|
168
|
+
<lockscope><exclusive/></lockscope>
|
|
169
|
+
<locktoken><href>${token}</href></locktoken>
|
|
170
|
+
<lockroot><href>${path}</href></lockroot>
|
|
171
|
+
<depth>0</depth>
|
|
172
|
+
<timeout>Second-${seconds}</timeout>
|
|
173
|
+
</activelock></lockdiscovery></prop>`;
|
|
174
|
+
return true;
|
|
175
|
+
}
|
|
176
|
+
if (ctx.method === 'PROPFIND') {
|
|
177
|
+
setWebdavHeaders();
|
|
178
|
+
const node = await (0, vfs_1.urlToNode)(path, ctx);
|
|
179
|
+
if (!node)
|
|
180
|
+
return;
|
|
181
|
+
let depth = Number(ctx.get('depth'));
|
|
182
|
+
depth = isNaN(depth) ? Infinity : depth;
|
|
183
|
+
const isList = depth !== 0;
|
|
184
|
+
if ((0, vfs_1.statusCodeForMissingPerm)(node, isList ? 'can_list' : 'can_see', ctx)) {
|
|
185
|
+
if (ctx.status === cross_1.HTTP_UNAUTHORIZED)
|
|
186
|
+
setWebdavHeaders(true);
|
|
187
|
+
return true;
|
|
188
|
+
}
|
|
189
|
+
ctx.type = 'xml';
|
|
190
|
+
ctx.status = 207;
|
|
191
|
+
const pathSlash = (0, cross_1.enforceFinal)('/', path);
|
|
192
|
+
const res = ctx.body = new stream_1.PassThrough({ encoding: 'utf8' });
|
|
193
|
+
res.write(`<?xml version="1.0" encoding="utf-8" ?><multistatus xmlns="DAV:">`);
|
|
194
|
+
await sendEntry(node);
|
|
195
|
+
if (isList) {
|
|
196
|
+
depth = Math.max(0, depth - 1);
|
|
197
|
+
for await (const n of (0, vfs_1.walkNode)(node, { ctx, depth }))
|
|
198
|
+
await sendEntry(n, true);
|
|
199
|
+
}
|
|
200
|
+
res.write(`</multistatus>`);
|
|
201
|
+
res.end();
|
|
202
|
+
return true;
|
|
203
|
+
async function sendEntry(node, append = false) {
|
|
204
|
+
if ((0, vfs_1.nodeIsLink)(node))
|
|
205
|
+
return;
|
|
206
|
+
const name = (0, vfs_1.getNodeName)(node);
|
|
207
|
+
const isDir = await (0, vfs_1.nodeIsFolder)(node);
|
|
208
|
+
const st = await (0, vfs_1.nodeStats)(node);
|
|
209
|
+
res.write(`<response>
|
|
210
|
+
<href>${pathSlash + (append ? (0, cross_1.pathEncode)(name, true) + (isDir ? '/' : '') : '')}</href>
|
|
211
|
+
<propstat>
|
|
212
|
+
<status>HTTP/1.1 200 OK</status>
|
|
213
|
+
<prop>
|
|
214
|
+
${(0, cross_1.prefix)('<getlastmodified>', st?.mtime?.toGMTString(), '</getlastmodified>')}
|
|
215
|
+
${(0, cross_1.prefix)('<creationdate>', (st?.birthtime || st?.ctime)?.toISOString().replace(/\..*/, '-00:00'), '</creationdate>')}
|
|
216
|
+
${isDir ? '<resourcetype><collection/></resourcetype>'
|
|
217
|
+
: `<resourcetype/><getcontentlength>${st?.size}</getcontentlength>`}
|
|
218
|
+
</prop>
|
|
219
|
+
</propstat>
|
|
220
|
+
</response>
|
|
221
|
+
`);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
if (ctx.method === 'PROPPATCH') {
|
|
225
|
+
setWebdavHeaders();
|
|
226
|
+
if (isLocked(path, ctx))
|
|
227
|
+
return true;
|
|
228
|
+
const node = await (0, vfs_1.urlToNode)(path, ctx);
|
|
229
|
+
if (!node)
|
|
230
|
+
return;
|
|
231
|
+
if ((0, vfs_1.statusCodeForMissingPerm)(node, 'can_see', ctx)) {
|
|
232
|
+
if (ctx.status === cross_1.HTTP_UNAUTHORIZED)
|
|
233
|
+
setWebdavHeaders(true);
|
|
234
|
+
return true;
|
|
235
|
+
}
|
|
236
|
+
ctx.type = 'xml';
|
|
237
|
+
ctx.status = 207;
|
|
238
|
+
ctx.body = `<?xml version="1.0" encoding="utf-8"?>
|
|
239
|
+
<multistatus xmlns="DAV:">
|
|
240
|
+
<response>
|
|
241
|
+
<href>${path}</href>
|
|
242
|
+
<propstat>
|
|
243
|
+
<status>HTTP/1.1 200 OK</status>
|
|
244
|
+
<prop/>
|
|
245
|
+
</propstat>
|
|
246
|
+
</response>
|
|
247
|
+
</multistatus>`;
|
|
248
|
+
return true;
|
|
249
|
+
}
|
|
250
|
+
function setWebdavHeaders(authenticate = false) {
|
|
251
|
+
ctx.set('DAV', '1,2');
|
|
252
|
+
ctx.set('MS-Author-Via', 'DAV');
|
|
253
|
+
ctx.set('Allow', 'PROPFIND,PROPPATCH,OPTIONS,DELETE,MOVE,LOCK,UNLOCK,MKCOL,PUT');
|
|
254
|
+
if (authenticate)
|
|
255
|
+
ctx.set('WWW-Authenticate', `Basic realm="HFS WebDAV"`); // keep a dedicated realm for WebDAV so Windows credential cache is isolated from other basic-auth flows
|
|
256
|
+
}
|
|
257
|
+
function shouldChallengeWebdav() {
|
|
258
|
+
if ((0, auth_1.getCurrentUsername)(ctx))
|
|
259
|
+
return false;
|
|
260
|
+
if (forceWebdavLogin.compiled()?.test(ua))
|
|
261
|
+
return challengeWebdav();
|
|
262
|
+
if (!webdavInitialAuth.compiled()?.test(ua))
|
|
263
|
+
return false;
|
|
264
|
+
if (ctx.get('authorization'))
|
|
265
|
+
return challengeWebdav();
|
|
266
|
+
const key = `${ctx.ip}|${ctx.host}|${ua || ''}`;
|
|
267
|
+
if (webdavPrompted.has(key))
|
|
268
|
+
return false;
|
|
269
|
+
webdavPrompted.try(key, () => true);
|
|
270
|
+
return challengeWebdav();
|
|
271
|
+
function challengeWebdav() {
|
|
272
|
+
setWebdavHeaders(true);
|
|
273
|
+
ctx.status = cross_1.HTTP_UNAUTHORIZED;
|
|
274
|
+
ctx.body = '';
|
|
275
|
+
return true;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
function compileWebdavAgentRegex(v) {
|
|
280
|
+
return !v ? null : v === true ? /.*/ : new RegExp(v.trim(), 'i');
|
|
281
|
+
}
|
|
282
|
+
// Finder will upload special attributes as files with name ._* that can be merged using system utility "dot_clean"
|
|
283
|
+
const cleaners = {};
|
|
284
|
+
function dotClean(path) {
|
|
285
|
+
(0, cross_1.getOrSet)(cleaners, path, () => setTimeout(() => {
|
|
286
|
+
try {
|
|
287
|
+
(0, child_process_1.exec)('dot_clean .', { cwd: path }, (err, out) => done(err || out));
|
|
288
|
+
}
|
|
289
|
+
catch (e) {
|
|
290
|
+
done(e);
|
|
291
|
+
}
|
|
292
|
+
function done(log) {
|
|
293
|
+
console.debug('dot_clean', path, log);
|
|
294
|
+
delete cleaners[path];
|
|
295
|
+
}
|
|
296
|
+
}, 10_000));
|
|
297
|
+
}
|