hfs 3.2.0-alpha1.1 → 3.2.0-beta3
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/{af-RjyQ-neT.js → af-CV3suok4.js} +1 -1
- package/admin/assets/{am-PptHpglb.js → am-DNvTMYJ-.js} +1 -1
- package/admin/assets/{ar-Du3syDxa.js → ar-BGIEjplY.js} +1 -1
- package/admin/assets/{ar-dz-CiziRh1x.js → ar-dz-Ilq3cpjW.js} +1 -1
- package/admin/assets/{ar-iq-hCKg0CRP.js → ar-iq-DjXqm3R3.js} +1 -1
- package/admin/assets/{ar-kw-COYePt-p.js → ar-kw-4nugD0ui.js} +1 -1
- package/admin/assets/{ar-ly-CY5yETqb.js → ar-ly-DvrpBGjb.js} +1 -1
- package/admin/assets/{ar-ma-DczXi97r.js → ar-ma-ClnDMaR_.js} +1 -1
- package/admin/assets/{ar-sa-Bu7sTKzl.js → ar-sa-yyW34p_k.js} +1 -1
- package/admin/assets/{ar-tn-BrG0r6qZ.js → ar-tn-Dkn_F6cH.js} +1 -1
- package/admin/assets/{az-DYRCrdk0.js → az-BGQIRIME.js} +1 -1
- package/admin/assets/{be-DObSs5HX.js → be-CgBB_Tl_.js} +1 -1
- package/admin/assets/{bg-BDBkMe50.js → bg-DQ2NnjXf.js} +1 -1
- package/admin/assets/{bi-CApXFwc6.js → bi-C76InPXJ.js} +1 -1
- package/admin/assets/{bm-CjxCV8Lr.js → bm-CEihEziX.js} +1 -1
- package/admin/assets/{bn-DzWZbDi3.js → bn-CxCmr3_d.js} +1 -1
- package/admin/assets/{bn-bd-ijdPRJbb.js → bn-bd-hXn474is.js} +1 -1
- package/admin/assets/{bo-BDDfj5XH.js → bo-23wkHeLT.js} +1 -1
- package/admin/assets/{br-DRHEjait.js → br-DwfJ5fK1.js} +1 -1
- package/admin/assets/{bs-B0Zv2Agm.js → bs-pRqCggJt.js} +1 -1
- package/admin/assets/{ca-DuZAUqU_.js → ca-DgXusSYE.js} +1 -1
- package/admin/assets/{cs-DVkylosR.js → cs-CDakLSd0.js} +1 -1
- package/admin/assets/{cv-8Is1Fns9.js → cv-LGuw2gsy.js} +1 -1
- package/admin/assets/{cy-Bk30Or_y.js → cy--OuAdcJb.js} +1 -1
- package/admin/assets/{da-C41DIdZK.js → da-BY4-Oo6j.js} +1 -1
- package/admin/assets/{de--qvLJgIE.js → de-DhZFVtF3.js} +1 -1
- package/admin/assets/{de-at-D5vCBlsw.js → de-at-Dyk0Il-9.js} +1 -1
- package/admin/assets/{de-ch-Oh2cO23c.js → de-ch-C9HgqgBG.js} +1 -1
- package/admin/assets/{dv-4ZO2KNbw.js → dv-BnHXOAHb.js} +1 -1
- package/admin/assets/{el-CobOUqyu.js → el-DKZwotls.js} +1 -1
- package/admin/assets/{en-C9afClpS.js → en-Cfkmutmh.js} +1 -1
- package/admin/assets/{en-au-C_QMghEl.js → en-au-B1uLNVRS.js} +1 -1
- package/admin/assets/{en-ca-C80KILwc.js → en-ca-CPQ8JLrR.js} +1 -1
- package/admin/assets/{en-gb-BwQ0asAI.js → en-gb-BBXjeXVa.js} +1 -1
- package/admin/assets/{en-ie-ConCcQkq.js → en-ie-wbMYxxCu.js} +1 -1
- package/admin/assets/{en-il-CAOzD4DD.js → en-il-BqiJQjg0.js} +1 -1
- package/admin/assets/{en-in-87gardaO.js → en-in-Cox_dy1Y.js} +1 -1
- package/admin/assets/{en-nz-uMTjgkDP.js → en-nz-D2denFwV.js} +1 -1
- package/admin/assets/{en-sg-BlbiLv4L.js → en-sg-CPMvhL_f.js} +1 -1
- package/admin/assets/{en-tt-BXXmBax2.js → en-tt-DTr6jsXn.js} +1 -1
- package/admin/assets/{eo-BBL7o-0W.js → eo-Dz5-PLu9.js} +1 -1
- package/admin/assets/{es-DuNej-79.js → es-Ctd1zmr5.js} +1 -1
- package/admin/assets/{es-do-bZWKFwTE.js → es-do-C1siaHVl.js} +1 -1
- package/admin/assets/{es-mx-C2Lfb86F.js → es-mx-xN8Flblv.js} +1 -1
- package/admin/assets/{es-pr-wU3Du8m-.js → es-pr-CdDKn0Nn.js} +1 -1
- package/admin/assets/{es-us-qcU-zRiw.js → es-us-DrylVZZL.js} +1 -1
- package/admin/assets/{et-B-4PjN_n.js → et-Bm2qrgIv.js} +1 -1
- package/admin/assets/{eu-D0pNDZ9i.js → eu-B8Npz2me.js} +1 -1
- package/admin/assets/{fa-R_zmYBGc.js → fa-Ce478gVV.js} +1 -1
- package/admin/assets/{fi-CWYS_0Hg.js → fi-DmQCLn1D.js} +1 -1
- package/admin/assets/{fo-aJYpcnWS.js → fo-ty-wZYb3.js} +1 -1
- package/admin/assets/{fr-BHFrWfwB.js → fr-CPfNx_dE.js} +1 -1
- package/admin/assets/{fr-ca-ihwmO47n.js → fr-ca-DUuUTvVd.js} +1 -1
- package/admin/assets/{fr-ch-Cb8XbRfE.js → fr-ch-DN707oy8.js} +1 -1
- package/admin/assets/{fy-DT9BUc6t.js → fy-DU59ExaB.js} +1 -1
- package/admin/assets/{ga-B8p4naXJ.js → ga-VymLo-KV.js} +1 -1
- package/admin/assets/{gd-Dzrl6rPc.js → gd-OonH0A5f.js} +1 -1
- package/admin/assets/{gl-bN2tXl9d.js → gl-BINpSrwL.js} +1 -1
- package/admin/assets/{gom-latn-DUK8PgIJ.js → gom-latn-CDC4oAAB.js} +1 -1
- package/admin/assets/{gu-CxAIP8fw.js → gu-EP-tB-Px.js} +1 -1
- package/admin/assets/{he-Bm9XJVAj.js → he-BWkV26db.js} +1 -1
- package/admin/assets/{hi-CHik9Qxx.js → hi-CV9_k_t0.js} +1 -1
- package/admin/assets/{hr-Dn57_dqN.js → hr-BXciZ7cA.js} +1 -1
- package/admin/assets/{ht-BRS-FrJq.js → ht-CzKVwgZT.js} +1 -1
- package/admin/assets/{hu-hVaGcZ-U.js → hu-TogNJljg.js} +1 -1
- package/admin/assets/{hy-am-heLxXhpz.js → hy-am-B5AuRs7s.js} +1 -1
- package/admin/assets/{id-B6AkO4h8.js → id-Cz1E3N6j.js} +1 -1
- package/admin/assets/{index-Buyl8rI8.js → index-HuChhgks.js} +1 -1
- package/admin/assets/{index-WXxQwyJV.js → index-vGnRRM8b.js} +104 -104
- package/admin/assets/{is-BUADPoni.js → is-5T5BMRUs.js} +1 -1
- package/admin/assets/{it-Ce3gssOP.js → it-BVocdL64.js} +1 -1
- package/admin/assets/{it-ch-CJzj2czr.js → it-ch-g40Nau9_.js} +1 -1
- package/admin/assets/{ja-DykMIu-9.js → ja-B20OfIuQ.js} +1 -1
- package/admin/assets/{jv-Cum6DLwa.js → jv-BHABcRuK.js} +1 -1
- package/admin/assets/{ka-foCPVFxw.js → ka-n41vrxKm.js} +1 -1
- package/admin/assets/{kk-CMNu1ysC.js → kk-Wbgu12fS.js} +1 -1
- package/admin/assets/{km-BfMisGaD.js → km-CKhKBloy.js} +1 -1
- package/admin/assets/{kn-R_9S5KJz.js → kn-CGuQntHB.js} +1 -1
- package/admin/assets/{ko-DYRND-mM.js → ko-CezL4UDJ.js} +1 -1
- package/admin/assets/{ku-CNZ_weGy.js → ku-BxSpmtCp.js} +1 -1
- package/admin/assets/{ky-BhTAWL4I.js → ky-C0SyLnPR.js} +1 -1
- package/admin/assets/{lb-CxWhQxwF.js → lb-Y7KwDWXU.js} +1 -1
- package/admin/assets/{lo-DBL_XLkE.js → lo-BSFQGbEr.js} +1 -1
- package/admin/assets/{lt-BakZVm4p.js → lt-hJzN-eOW.js} +1 -1
- package/admin/assets/{lv-CCn-slXG.js → lv-C_oDREN9.js} +1 -1
- package/admin/assets/{me-xPEA4qjE.js → me-BEBNCwj6.js} +1 -1
- package/admin/assets/{mi-6cCs2Mzx.js → mi-CxoHQu_r.js} +1 -1
- package/admin/assets/{mk-CwWbHJPS.js → mk-CMde5Lyr.js} +1 -1
- package/admin/assets/{ml-Dp7Ab1SV.js → ml-Bb1prImZ.js} +1 -1
- package/admin/assets/{mn-ClotGWAe.js → mn-Cbig8iIC.js} +1 -1
- package/admin/assets/{mr-GiM-paTF.js → mr-C2OpIZbx.js} +1 -1
- package/admin/assets/{ms-BiZ1_eVR.js → ms-DEYcAuJ5.js} +1 -1
- package/admin/assets/{ms-my-oevI0fOH.js → ms-my-B3WZsidY.js} +1 -1
- package/admin/assets/{mt-BaIHeOl1.js → mt-qLOys0a8.js} +1 -1
- package/admin/assets/{my-Cg5Fc_1j.js → my-DRfLOxB3.js} +1 -1
- package/admin/assets/{nb-ZPODjT6R.js → nb-CgMDn1Zp.js} +1 -1
- package/admin/assets/{ne-C0ti-ZX2.js → ne-D_QxFLXg.js} +1 -1
- package/admin/assets/{nl-DP2PkkGx.js → nl-ByoNxF5J.js} +1 -1
- package/admin/assets/{nl-be-VPC0QyaW.js → nl-be-i74dNB1Q.js} +1 -1
- package/admin/assets/{nn-NLidKyJd.js → nn-CXXinLMW.js} +1 -1
- package/admin/assets/{oc-lnc-Ddj9_PnD.js → oc-lnc-COJUbhtG.js} +1 -1
- package/admin/assets/{pa-in-DSAWffhb.js → pa-in-CS1YXP4F.js} +1 -1
- package/admin/assets/{pl-Yojex5aS.js → pl-C8IUihXp.js} +1 -1
- package/admin/assets/{pt-BQMs2la2.js → pt-Bb-_2exC.js} +1 -1
- package/admin/assets/{pt-br-DVeeNZu8.js → pt-br-CsJVJZP0.js} +1 -1
- package/admin/assets/{rn-CKkTrK3f.js → rn-Bnqkr3Ve.js} +1 -1
- package/admin/assets/{ro-BbgGULSZ.js → ro-F686nvw6.js} +1 -1
- package/admin/assets/{ru-mAZX3DTa.js → ru-De6t8zEr.js} +1 -1
- package/admin/assets/{rw-CYErOxKd.js → rw-dgQlpJrj.js} +1 -1
- package/admin/assets/{sd-kfPTqkSy.js → sd-D0kCoi0j.js} +1 -1
- package/admin/assets/{se-D0bN3rDS.js → se-CsHkKyBJ.js} +1 -1
- package/admin/assets/{sha512-99nhg44S.js → sha512-CwhW8y7u.js} +1 -1
- package/admin/assets/{si-COjb_4Hy.js → si-BCtFhatF.js} +1 -1
- package/admin/assets/{sk-Co95XNOo.js → sk-HU0zEULD.js} +1 -1
- package/admin/assets/{sl-DUXa5wTF.js → sl-pswBfYoS.js} +1 -1
- package/admin/assets/{sq-B-KU0nWK.js → sq-CBhNzauG.js} +1 -1
- package/admin/assets/{sr-Cb_b-zPG.js → sr-Ba_jL5gy.js} +1 -1
- package/admin/assets/{sr-cyrl-csuTDIB6.js → sr-cyrl-Cu3CgIND.js} +1 -1
- package/admin/assets/{ss-BDnE-cG6.js → ss-CgQ9MxWY.js} +1 -1
- package/admin/assets/{sv-CzWdOHcE.js → sv-D-BB39HA.js} +1 -1
- package/admin/assets/{sv-fi-IK8oi5nB.js → sv-fi-B-o1NvLZ.js} +1 -1
- package/admin/assets/{sw-fPHo5hof.js → sw-BLvaZOOT.js} +1 -1
- package/admin/assets/{ta-TteN0nyU.js → ta-DuDUH7cy.js} +1 -1
- package/admin/assets/{te-2gsQb1fF.js → te-CEN7Z7sm.js} +1 -1
- package/admin/assets/{tet-ClTpDYJv.js → tet-BQlE9nRY.js} +1 -1
- package/admin/assets/{tg-Bel7Uc6z.js → tg-D_Ri8xyt.js} +1 -1
- package/admin/assets/{th-pDQttM9V.js → th-Cni5syVw.js} +1 -1
- package/admin/assets/{tk-CxoKYZH6.js → tk-DpSrobWM.js} +1 -1
- package/admin/assets/{tl-ph-D9htRcOQ.js → tl-ph-DZSUk-Vh.js} +1 -1
- package/admin/assets/{tlh-7UqvDBxU.js → tlh-BbZfw6gL.js} +1 -1
- package/admin/assets/{tr-NjJrq1iC.js → tr-CDpse5eg.js} +1 -1
- package/admin/assets/{tzl-CzGaXn8M.js → tzl-Cw8O9VLh.js} +1 -1
- package/admin/assets/{tzm-BiuscZFr.js → tzm-MQi7pP8P.js} +1 -1
- package/admin/assets/{tzm-latn-IeWkCgWf.js → tzm-latn-B0fBCe7c.js} +1 -1
- package/admin/assets/{ug-cn-5LK64x5v.js → ug-cn-CryBI7iC.js} +1 -1
- package/admin/assets/{uk-CzacUaQe.js → uk-ehtRqDfR.js} +1 -1
- package/admin/assets/{ur-BRV7z4Gu.js → ur-DLUufiXP.js} +1 -1
- package/admin/assets/{uz-C4G77QOI.js → uz-BGWw0Xm-.js} +1 -1
- package/admin/assets/{uz-latn-CQFrzZ6F.js → uz-latn-_11BzAge.js} +1 -1
- package/admin/assets/{vi-DEFXdlJi.js → vi-B1XZb9GR.js} +1 -1
- package/admin/assets/{x-pseudo-sWj5RKxR.js → x-pseudo-DbSvrJEU.js} +1 -1
- package/admin/assets/{yo-CBDjk96e.js → yo-enfGQNrL.js} +1 -1
- package/admin/assets/{zh-CQtX_fkD.js → zh-SltlpEBf.js} +1 -1
- package/admin/assets/{zh-cn-D6dF0jKM.js → zh-cn-C5GmIAnm.js} +1 -1
- package/admin/assets/{zh-hk-DY-Jaqdg.js → zh-hk-DHOlvPn8.js} +1 -1
- package/admin/assets/{zh-tw-BdmVby0R.js → zh-tw-Bk63QN63.js} +1 -1
- package/admin/index.html +1 -1
- package/frontend/assets/index-legacy-CU5HXdA4.js +9 -0
- package/frontend/assets/{index-legacy-CQovmh_0.js → index-legacy-FmhiYDuG.js} +1 -1
- package/frontend/assets/{sha512-legacy-CXU3efCO.js → sha512-legacy-B8oB-JQf.js} +1 -1
- package/frontend/index.html +1 -1
- package/npm-shrinkwrap.json +75 -31
- package/package.json +12 -12
- package/src/adminApis.js +2 -2
- package/src/api.get_file_list.js +3 -3
- package/src/api.vfs.js +46 -1
- package/src/basicWeb.js +1 -1
- package/src/comments.js +7 -4
- package/src/const.js +1 -1
- package/src/cross.js +12 -10
- package/src/events.js +2 -3
- package/src/expiringCache.js +8 -7
- package/src/frontEndApis.js +17 -14
- package/src/geo.js +6 -5
- package/src/listen.js +3 -2
- package/src/middlewares.js +1 -1
- package/src/nat.js +4 -1
- package/src/perm.js +1 -1
- package/src/plugins.js +2 -2
- package/src/roots.js +2 -2
- package/src/selfCheck.js +2 -1
- package/src/serveGuiAndSharedFiles.js +17 -7
- package/src/serveGuiFiles.js +3 -3
- package/src/update.js +1 -3
- package/src/util-files.js +9 -4
- package/src/util-http.js +2 -0
- package/src/vfs.js +20 -8
- package/src/zip.js +1 -1
- package/frontend/assets/index-legacy-BtoTkZho.js +0 -9
package/src/api.vfs.js
CHANGED
|
@@ -17,6 +17,7 @@ const util_os_1 = require("./util-os");
|
|
|
17
17
|
const listen_1 = require("./listen");
|
|
18
18
|
const SendList_1 = require("./SendList");
|
|
19
19
|
const walkDir_1 = require("./walkDir");
|
|
20
|
+
const roots_1 = require("./roots");
|
|
20
21
|
// to manipulate the tree we need the original node
|
|
21
22
|
async function urlToNodeOriginal(uri) {
|
|
22
23
|
const n = await (0, vfs_1.urlToNode)(uri);
|
|
@@ -51,7 +52,7 @@ exports.default = {
|
|
|
51
52
|
};
|
|
52
53
|
}
|
|
53
54
|
},
|
|
54
|
-
async set_vfs({ uri, props }) {
|
|
55
|
+
async set_vfs({ uri, props, uriRemaps = {} }) {
|
|
55
56
|
const n = uri && await urlToNodeOriginal(uri);
|
|
56
57
|
if (!n)
|
|
57
58
|
return new apiMiddleware_1.ApiError(const_1.HTTP_NOT_FOUND, 'path not found');
|
|
@@ -67,7 +68,13 @@ exports.default = {
|
|
|
67
68
|
simplifyName(n);
|
|
68
69
|
n.isFolder = undefined; // reset field, it will be set by saveVfs
|
|
69
70
|
await (0, vfs_1.saveVfs)();
|
|
71
|
+
if (!(0, vfs_1.isRoot)(n)) // not actually used by admin-panel but still
|
|
72
|
+
uriRemaps[uri] = uriForNode(uri, n); // just in case the current node was modified
|
|
73
|
+
await updateRootsForVfsUriRemaps(uriRemaps);
|
|
70
74
|
return n;
|
|
75
|
+
function uriForNode(uri, n) {
|
|
76
|
+
return (0, misc_1.enforceFinal)('/', (0, path_1.dirname)(uri).replace(/\\/g, '/')) + (0, misc_1.pathEncode)((0, vfs_1.getNodeName)(n)) + ((0, vfs_1.nodeIsFolder)(n) ? '/' : '');
|
|
77
|
+
}
|
|
71
78
|
},
|
|
72
79
|
// legacy – not currently used by the UI
|
|
73
80
|
async move_vfs({ from, parent }) {
|
|
@@ -94,6 +101,9 @@ exports.default = {
|
|
|
94
101
|
;
|
|
95
102
|
(parentNode.children ||= []).push(fromNode);
|
|
96
103
|
await (0, vfs_1.saveVfs)();
|
|
104
|
+
await updateRootsForVfsUriRemaps({
|
|
105
|
+
[from]: (0, misc_1.enforceFinal)('/', parent) + (0, misc_1.pathEncode)(name) + ((0, vfs_1.nodeIsFolder)(fromNode) ? '/' : '')
|
|
106
|
+
});
|
|
97
107
|
return {};
|
|
98
108
|
},
|
|
99
109
|
// legacy – not currently used by the UI
|
|
@@ -271,6 +281,41 @@ function sanitizeVfsProps(props) {
|
|
|
271
281
|
: props.children.map(sanitizeVfsProps);
|
|
272
282
|
return ret;
|
|
273
283
|
}
|
|
284
|
+
function updateRootsForVfsUriRemaps(uriRemaps = {}) {
|
|
285
|
+
const remaps = Object.entries(uriRemaps)
|
|
286
|
+
.map(([from, to]) => [normalizeVfsId(from), normalizeVfsId(to)])
|
|
287
|
+
.filter(x => x[0] !== x[1])
|
|
288
|
+
.sort(([a], [b]) => b.length - a.length); // longest first because, in case of both parent and child, it's more specific
|
|
289
|
+
let changed = false;
|
|
290
|
+
const updatedRoots = lodash_1.default.mapValues(roots_1.roots.get(), root => {
|
|
291
|
+
if (typeof root !== 'string' || !root)
|
|
292
|
+
return root;
|
|
293
|
+
const normalizedRoot = normalize(root);
|
|
294
|
+
for (const [from, to] of remaps) {
|
|
295
|
+
// roots are stored outside the VFS tree, so rename/move edits need an explicit path remap
|
|
296
|
+
const remappedRoot = replaceUriPrefix(normalizedRoot, from, to);
|
|
297
|
+
if (!remappedRoot)
|
|
298
|
+
continue;
|
|
299
|
+
if (remappedRoot !== root)
|
|
300
|
+
changed = true;
|
|
301
|
+
return remappedRoot;
|
|
302
|
+
}
|
|
303
|
+
return root;
|
|
304
|
+
});
|
|
305
|
+
if (changed)
|
|
306
|
+
roots_1.roots.set(updatedRoots);
|
|
307
|
+
function normalize(uri) {
|
|
308
|
+
return String(uri).replace(/^\/+|^(?!\/)|\/{2,}|\/+$|(?<!\/)$/g, '/');
|
|
309
|
+
}
|
|
310
|
+
function normalizeVfsId(uri) {
|
|
311
|
+
return normalize((0, misc_1.pathDecode)(String(uri))); // admin remaps come from tree ids, while roots are stored as readable VFS paths
|
|
312
|
+
}
|
|
313
|
+
function replaceUriPrefix(uri, oldPrefix, newPrefix) {
|
|
314
|
+
return uri === oldPrefix ? newPrefix
|
|
315
|
+
: uri.startsWith(oldPrefix) ? newPrefix + uri.slice(oldPrefix.length)
|
|
316
|
+
: '';
|
|
317
|
+
}
|
|
318
|
+
}
|
|
274
319
|
function simplifyName(node) {
|
|
275
320
|
const { name, ...noName } = node;
|
|
276
321
|
if ((0, vfs_1.getNodeName)(noName) === name)
|
package/src/basicWeb.js
CHANGED
|
@@ -41,7 +41,7 @@ function basicWeb(ctx, node) {
|
|
|
41
41
|
const stream = (0, misc_1.asyncGeneratorToReadable)((0, misc_1.filterMapGenerator)(walker, async (el) => {
|
|
42
42
|
const isFolder = (0, vfs_1.nodeIsFolder)(el);
|
|
43
43
|
const name = (0, vfs_1.getNodeName)(el) + (isFolder ? '/' : '');
|
|
44
|
-
return `<li>${a((0, misc_1.pathEncode)(name) + (isFolder && !await (0, vfs_1.
|
|
44
|
+
return `<li>${a((0, misc_1.pathEncode)(name) + (isFolder && !await (0, vfs_1.getDefaultFile)(el, ctx) ? force : ''), name)}\n`;
|
|
45
45
|
}));
|
|
46
46
|
ctx.body = stream;
|
|
47
47
|
stream.push(`<meta name="viewport" content="width=device-width" />`);
|
package/src/comments.js
CHANGED
|
@@ -16,6 +16,7 @@ const misc_1 = require("./misc");
|
|
|
16
16
|
const lodash_1 = __importDefault(require("lodash"));
|
|
17
17
|
const iconv_lite_1 = __importDefault(require("iconv-lite"));
|
|
18
18
|
const promises_1 = require("node:fs/promises");
|
|
19
|
+
const expiringCache_1 = require("./expiringCache");
|
|
19
20
|
exports.DESCRIPT_ION = 'descript.ion';
|
|
20
21
|
exports.DESCRIPT_ION_ALT = 'DESCRIPT.ION';
|
|
21
22
|
const commentsStorage = (0, config_1.defineConfig)(cross_1.CFG.comments_storage, '', v => ['', 'attr+ion'].includes(v)); // compiled tell us if we are using descript.ion
|
|
@@ -81,13 +82,15 @@ async function filePathHelper(folder) {
|
|
|
81
82
|
return await (0, util_files_1.exists)(alt) && !await (0, util_files_1.exists)(main) ? alt : main;
|
|
82
83
|
}
|
|
83
84
|
const MULTILINE_SUFFIX = Buffer.from([4, 0xC2]);
|
|
85
|
+
const pathCache = (0, expiringCache_1.expiringCache)(2_000);
|
|
86
|
+
// this can be called many times when listing a folder, and we want to also not check too often as it can be expensive, especially on a networked drive
|
|
84
87
|
async function readDescriptIon(path) {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
// for simplicity we "remove" the sequence MULTILINE_SUFFIX before iconv.decode messes it up
|
|
88
|
+
return (0, util_files_1.parseFile)(await pathCache.try(path, filePathHelper), raw => {
|
|
89
|
+
// for simplicity, we "remove" the sequence MULTILINE_SUFFIX before iconv.decode messes it up
|
|
88
90
|
for (let i = 0; i < raw.length; i++)
|
|
89
91
|
if (raw[i] === MULTILINE_SUFFIX[0] && raw[i + 1] === MULTILINE_SUFFIX[1] && [undefined, 13, 10].includes(raw[i + 2]))
|
|
90
92
|
raw[i] = raw[i + 1] = 10;
|
|
93
|
+
// decoding could also be done with native TextDecoder.decode, but we need iconv for the encoding anyway
|
|
91
94
|
const decoded = iconv_lite_1.default.decode(raw, descriptIonEncoding.get());
|
|
92
95
|
const ret = new Map(decoded.split('\n').map(line => {
|
|
93
96
|
const quoted = line[0] === '"' ? 1 : 0;
|
|
@@ -98,7 +101,7 @@ async function readDescriptIon(path) {
|
|
|
98
101
|
}));
|
|
99
102
|
ret.delete('');
|
|
100
103
|
return ret;
|
|
101
|
-
});
|
|
104
|
+
}, 2000);
|
|
102
105
|
}
|
|
103
106
|
descriptIonEncoding.sub(() => {
|
|
104
107
|
for (const k of util_files_1.parseFileCache.keys())
|
package/src/const.js
CHANGED
|
@@ -68,7 +68,7 @@ exports.DEV = process.env.DEV ? 'DEV' : '';
|
|
|
68
68
|
exports.ORIGINAL_CWD = process.cwd();
|
|
69
69
|
exports.HFS_STARTED = new Date();
|
|
70
70
|
const PKG_PATH = (0, path_1.join)(__dirname, '..', 'package.json');
|
|
71
|
-
exports.BUILD_TIMESTAMP =
|
|
71
|
+
exports.BUILD_TIMESTAMP = fs.statSync(PKG_PATH).mtime.toISOString();
|
|
72
72
|
const pkg = JSON.parse(fs.readFileSync(PKG_PATH, 'utf8'));
|
|
73
73
|
exports.VERSION = pkg.version;
|
|
74
74
|
exports.RUNNING_BETA = exports.VERSION.includes('-');
|
package/src/cross.js
CHANGED
|
@@ -17,7 +17,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
17
17
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
18
18
|
};
|
|
19
19
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
20
|
-
exports.VFS_STORED_KEYS = exports.PERM_KEYS = exports.defaultPerms = exports.WHO_ANY_ACCOUNT = exports.WHO_NO_ONE = exports.WHO_ANYONE = exports.LIST = exports.CFG = exports.THEME_OPTIONS = exports.SORT_BY_OPTIONS = exports.FRONTEND_OPTIONS = exports.MAX_TILE_SIZE = exports.DAY = exports.HOUR = exports.MINUTE = exports.WIKI_URL = exports.REPO_URL = exports.WEBSITE = void 0;
|
|
20
|
+
exports.VFS_STORED_KEYS = exports.PERM_KEYS = exports.defaultPerms = exports.WHO_ADMIN = exports.WHO_ANY_ACCOUNT = exports.WHO_NO_ONE = exports.WHO_ANYONE = exports.LIST = exports.CFG = exports.THEME_OPTIONS = exports.SORT_BY_OPTIONS = exports.FRONTEND_OPTIONS = exports.MAX_TILE_SIZE = exports.DAY = exports.HOUR = exports.MINUTE = exports.WIKI_URL = exports.REPO_URL = exports.WEBSITE = void 0;
|
|
21
21
|
exports.isWhoObject = isWhoObject;
|
|
22
22
|
exports.formatBytes = formatBytes;
|
|
23
23
|
exports.formatSpeed = formatSpeed;
|
|
@@ -25,7 +25,6 @@ exports.prefix = prefix;
|
|
|
25
25
|
exports.join = join;
|
|
26
26
|
exports.wait = wait;
|
|
27
27
|
exports.haveTimeout = haveTimeout;
|
|
28
|
-
exports.objSameKeys = objSameKeys;
|
|
29
28
|
exports.objFromKeys = objFromKeys;
|
|
30
29
|
exports.enforceFinal = enforceFinal;
|
|
31
30
|
exports.removeFinal = removeFinal;
|
|
@@ -81,6 +80,7 @@ exports.escapeHTML = escapeHTML;
|
|
|
81
80
|
exports.promiseBestEffort = promiseBestEffort;
|
|
82
81
|
exports.pathEncode = pathEncode;
|
|
83
82
|
exports.pathDecode = pathDecode;
|
|
83
|
+
exports.pathDecodeSegments = pathDecodeSegments;
|
|
84
84
|
exports.runAt = runAt;
|
|
85
85
|
exports.makeMatcher = makeMatcher;
|
|
86
86
|
exports.matches = matches;
|
|
@@ -127,18 +127,19 @@ exports.CFG = constMap(['geo_enable', 'geo_allow', 'geo_list', 'geo_allow_unknow
|
|
|
127
127
|
'log', 'error_log', 'log_rotation', 'dont_log_net', 'log_gui', 'log_api', 'log_ua', 'log_spam', 'track_ips',
|
|
128
128
|
'max_downloads', 'max_downloads_per_ip', 'max_downloads_per_account', 'roots', 'force_address', 'split_uploads',
|
|
129
129
|
'force_lang', 'suspend_plugins', 'base_url', 'size_1024', 'disable_custom_html', 'comments_storage',
|
|
130
|
-
'force_webdav_login', 'webdav_initial_auth', 'outbound_proxy', 'mapped_port', 'upnp_enabled']);
|
|
130
|
+
'force_webdav_login', 'webdav_initial_auth', 'outbound_proxy', 'mapped_port', 'upnp_enabled', 'show_uploader']);
|
|
131
131
|
exports.LIST = { add: '+', remove: '-', update: '=', props: 'props', ready: 'ready', error: 'e' };
|
|
132
132
|
exports.WHO_ANYONE = true;
|
|
133
133
|
exports.WHO_NO_ONE = false;
|
|
134
134
|
exports.WHO_ANY_ACCOUNT = '*';
|
|
135
|
+
exports.WHO_ADMIN = 'admin';
|
|
135
136
|
exports.defaultPerms = {
|
|
136
|
-
can_see: 'can_read',
|
|
137
137
|
can_read: exports.WHO_ANYONE,
|
|
138
|
+
can_see: 'can_read',
|
|
138
139
|
can_list: 'can_read',
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
140
|
+
can_archive: 'can_read',
|
|
141
|
+
can_upload: exports.WHO_ADMIN,
|
|
142
|
+
can_delete: exports.WHO_ADMIN,
|
|
142
143
|
};
|
|
143
144
|
exports.PERM_KEYS = typedKeys(exports.defaultPerms);
|
|
144
145
|
exports.VFS_STORED_KEYS = ['name', 'source', 'masks', 'default', 'accept', 'rename',
|
|
@@ -186,9 +187,6 @@ function haveTimeout(ms, job, error) {
|
|
|
186
187
|
new Promise((_resolve, reject) => h = setTimeout(() => reject(error || Error('timeout')), ms))
|
|
187
188
|
]);
|
|
188
189
|
}
|
|
189
|
-
function objSameKeys(src, newValue) {
|
|
190
|
-
return Object.fromEntries(Object.entries(src).map(([k, v]) => [k, newValue(v, k)]));
|
|
191
|
-
}
|
|
192
190
|
function objFromKeys(src, getValue) {
|
|
193
191
|
return Object.fromEntries(src.map(k => [k, getValue(k)]));
|
|
194
192
|
}
|
|
@@ -478,6 +476,10 @@ function pathEncode(s, all = false) {
|
|
|
478
476
|
function pathDecode(s) {
|
|
479
477
|
return decodeURI(s).replace(/%23/g, '#');
|
|
480
478
|
}
|
|
479
|
+
function pathDecodeSegments(s, map = String) {
|
|
480
|
+
// decode segment by segment so reserved escapes are decoded without turning encoded slashes into separators
|
|
481
|
+
return s.split('/').map(x => map(safeDecodeURIComponent(x)).replaceAll('/', '%2F')).join('/');
|
|
482
|
+
}
|
|
481
483
|
// run at a specific point in time, also solving the limit of setTimeout, which doesn't work with +32bit delays
|
|
482
484
|
function runAt(ts, cb) {
|
|
483
485
|
let cancel = false;
|
package/src/events.js
CHANGED
|
@@ -5,8 +5,7 @@ exports.BetterEventEmitter = void 0;
|
|
|
5
5
|
const LISTENERS_SUFFIX = '\0listeners';
|
|
6
6
|
class BetterEventEmitter {
|
|
7
7
|
listeners = new Map();
|
|
8
|
-
|
|
9
|
-
stop = this.preventDefault; // legacy pre-0.54 (introduced in 0.53)
|
|
8
|
+
stop = Symbol();
|
|
10
9
|
on(event, listener, { warnAfter = 10, callNow = false } = {}) {
|
|
11
10
|
if (typeof event === 'string')
|
|
12
11
|
event = [event];
|
|
@@ -89,7 +88,7 @@ class BetterEventEmitter {
|
|
|
89
88
|
const asyncRet = await Promise.all(syncRet);
|
|
90
89
|
return Object.assign(asyncRet, {
|
|
91
90
|
isDefaultPrevented: () => syncRet.isDefaultPrevented()
|
|
92
|
-
|| asyncRet.some((r) => r === this.
|
|
91
|
+
|| asyncRet.some((r) => r === this.stop)
|
|
93
92
|
});
|
|
94
93
|
}
|
|
95
94
|
}
|
package/src/expiringCache.js
CHANGED
|
@@ -6,24 +6,25 @@ function expiringCache(ttlMs) {
|
|
|
6
6
|
throw Error('invalid TTL');
|
|
7
7
|
const o = new Map();
|
|
8
8
|
return Object.assign(o, {
|
|
9
|
-
|
|
9
|
+
invalidate,
|
|
10
|
+
// creator can return undefined if the value should not be cached
|
|
10
11
|
try(k, creator) {
|
|
11
12
|
let ret = o.get(k);
|
|
12
13
|
if (ret === undefined) { // undefined = missing, as we don't accept this value in our cache
|
|
13
|
-
ret = creator(
|
|
14
|
+
ret = creator(k);
|
|
14
15
|
if (ret !== undefined) {
|
|
15
16
|
o.set(k, ret);
|
|
16
17
|
Promise.resolve(ret).then(v => {
|
|
17
18
|
if (v === undefined) // even in a promise, we'll consider undefined as a request to cancel the caching
|
|
18
|
-
invalidate();
|
|
19
|
+
invalidate(k);
|
|
19
20
|
}, () => { }) // avoid js warning
|
|
20
|
-
.finally(() => setTimeout(invalidate, ttlMs)); // wait for async (in case) before starting the timer
|
|
21
|
-
}
|
|
22
|
-
function invalidate() {
|
|
23
|
-
o.delete(k);
|
|
21
|
+
.finally(() => setTimeout(() => invalidate(k), ttlMs)); // wait for async (in case) before starting the timer
|
|
24
22
|
}
|
|
25
23
|
}
|
|
26
24
|
return ret;
|
|
27
25
|
},
|
|
28
26
|
});
|
|
27
|
+
function invalidate(k) {
|
|
28
|
+
o.delete(k);
|
|
29
|
+
}
|
|
29
30
|
}
|
package/src/frontEndApis.js
CHANGED
|
@@ -20,11 +20,13 @@ const promises_1 = require("fs/promises");
|
|
|
20
20
|
const path_1 = require("path");
|
|
21
21
|
const upload_1 = require("./upload");
|
|
22
22
|
const misc_1 = require("./misc");
|
|
23
|
+
const config_1 = require("./config");
|
|
23
24
|
const comments_1 = require("./comments");
|
|
24
25
|
const SendList_1 = require("./SendList");
|
|
25
26
|
const adminApis_1 = require("./adminApis");
|
|
26
27
|
const lodash_1 = __importDefault(require("lodash"));
|
|
27
28
|
const partialFolderSize = {};
|
|
29
|
+
const showUploader = (0, config_1.defineConfig)(misc_1.CFG.show_uploader, misc_1.WHO_ADMIN);
|
|
28
30
|
exports.frontEndApis = {
|
|
29
31
|
get_file_list: api_get_file_list_1.get_file_list,
|
|
30
32
|
...api_auth_1.authApis,
|
|
@@ -43,19 +45,20 @@ exports.frontEndApis = {
|
|
|
43
45
|
return new apiMiddleware_1.ApiError(const_1.HTTP_BAD_REQUEST, 'bad uris');
|
|
44
46
|
const isAdmin = (0, adminApis_1.ctxAdminAccess)(ctx);
|
|
45
47
|
return {
|
|
46
|
-
details:
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
48
|
+
details: (0, vfs_1.simpleWhoToError)(showUploader.get(), ctx) ? [] // return early because at the moment we only have the uploader
|
|
49
|
+
: await Promise.all(uris.map(async (uri) => {
|
|
50
|
+
if (typeof uri !== 'string')
|
|
51
|
+
return false; // false means error
|
|
52
|
+
const node = await (0, vfs_1.urlToNode)(uri, ctx);
|
|
53
|
+
if (!node || !(0, vfs_1.hasPermission)(node, 'can_see', ctx))
|
|
54
|
+
return false;
|
|
55
|
+
let upload = node.source && await (0, upload_1.getUploadMeta)(node.source).catch(() => undefined);
|
|
56
|
+
if (!upload)
|
|
57
|
+
return;
|
|
58
|
+
if (!isAdmin)
|
|
59
|
+
upload = lodash_1.default.omit(upload, 'ip');
|
|
60
|
+
return { upload };
|
|
61
|
+
}))
|
|
59
62
|
};
|
|
60
63
|
},
|
|
61
64
|
async create_folder({ uri, name }, ctx) {
|
|
@@ -126,7 +129,7 @@ exports.frontEndApis = {
|
|
|
126
129
|
await (0, comments_1.setCommentFor)(node.source, comment);
|
|
127
130
|
return {};
|
|
128
131
|
},
|
|
129
|
-
async get_folder_size_partial({ id }
|
|
132
|
+
async get_folder_size_partial({ id }) {
|
|
130
133
|
(0, misc_1.apiAssertTypes)({ string: { id } });
|
|
131
134
|
return partialFolderSize[id] || new apiMiddleware_1.ApiError(const_1.HTTP_NOT_FOUND);
|
|
132
135
|
},
|
package/src/geo.js
CHANGED
|
@@ -38,8 +38,8 @@ function isOpen() {
|
|
|
38
38
|
async function checkFiles() {
|
|
39
39
|
if (!enabled.get())
|
|
40
40
|
return;
|
|
41
|
-
const
|
|
42
|
-
const URL = `https://download.ip2location.com/lite/${
|
|
41
|
+
const BIN_FILE = 'IP2LOCATION-LITE-DB1.IPV6.BIN';
|
|
42
|
+
const URL = `https://download.ip2location.com/lite/${BIN_FILE}.ZIP`;
|
|
43
43
|
const LOCAL_FILE = 'geo_ip.bin';
|
|
44
44
|
const TEMP = LOCAL_FILE + '.downloading';
|
|
45
45
|
const { mtime = 0 } = await (0, misc_1.statWithTimeout)(LOCAL_FILE).catch(() => ({ mtime: 0 }));
|
|
@@ -49,11 +49,12 @@ async function checkFiles() {
|
|
|
49
49
|
try {
|
|
50
50
|
const req = await (0, misc_1.httpStream)(URL);
|
|
51
51
|
console.log(`Downloading ${name}`);
|
|
52
|
-
await (0, misc_1.unzip)(req, path => path.toUpperCase().endsWith(
|
|
53
|
-
await (0, misc_1.statWithTimeout)(TEMP); // check existence
|
|
52
|
+
await (0, misc_1.unzip)(req, path => path.toUpperCase().endsWith(BIN_FILE) && TEMP); // give a temp name
|
|
53
|
+
const s = await (0, misc_1.statWithTimeout)(TEMP); // check existence
|
|
54
|
+
if (s.size < 1E6)
|
|
55
|
+
throw `Bad size for geo_ip: ${s.size}`;
|
|
54
56
|
if (isOpen())
|
|
55
57
|
ip2location.close();
|
|
56
|
-
await (0, promises_1.unlink)(LOCAL_FILE).catch(() => { });
|
|
57
58
|
await (0, promises_1.rename)(TEMP, LOCAL_FILE);
|
|
58
59
|
exports.ip2country.cache.clear?.();
|
|
59
60
|
console.log(`${name} download completed`);
|
package/src/listen.js
CHANGED
|
@@ -137,7 +137,7 @@ function getCertObject() {
|
|
|
137
137
|
return;
|
|
138
138
|
const all = new crypto_1.X509Certificate(c);
|
|
139
139
|
const some = lodash_1.default.pick(all, ['subject', 'issuer', 'validFrom', 'validTo']);
|
|
140
|
-
const ret =
|
|
140
|
+
const ret = lodash_1.default.mapValues(some, v => v?.includes('=') ? Object.fromEntries(v.split('\n').map(x => x.split('='))) : v);
|
|
141
141
|
return Object.assign(ret, { altNames: all.subjectAltName?.replace(/DNS:/g, '').split(/, */) });
|
|
142
142
|
}
|
|
143
143
|
const considerHttps = (0, misc_1.debounceAsync)(async () => {
|
|
@@ -298,7 +298,8 @@ function stopServer(srv) {
|
|
|
298
298
|
console.debug("Failed to stop server", String(err));
|
|
299
299
|
resolve(err);
|
|
300
300
|
});
|
|
301
|
-
|
|
301
|
+
if (first_1.quitting)
|
|
302
|
+
srv.closeAllConnections();
|
|
302
303
|
});
|
|
303
304
|
}
|
|
304
305
|
function getCurrentPort(srv) {
|
package/src/middlewares.js
CHANGED
|
@@ -162,7 +162,7 @@ exports.paramsDecoder = paramsDecoder;
|
|
|
162
162
|
let internalSessionMw;
|
|
163
163
|
let options;
|
|
164
164
|
events_1.default.once('app', () => // wait for app to be defined
|
|
165
|
-
internalSessionMw = (0, koa_session_1.default)(options = { signed: true,
|
|
165
|
+
internalSessionMw = (0, koa_session_1.default)(options = { signed: true, renew: true, sameSite: 'lax' }, index_1.app));
|
|
166
166
|
const sessionMiddleware = (ctx, next) => {
|
|
167
167
|
options.key = 'hfs_' + ctx.protocol;
|
|
168
168
|
return internalSessionMw(ctx, next);
|
package/src/nat.js
CHANGED
|
@@ -76,7 +76,10 @@ exports.getPublicIps = (0, debounceAsync_1.debounceAsync)(async () => {
|
|
|
76
76
|
throw "no good";
|
|
77
77
|
return validIps;
|
|
78
78
|
}))));
|
|
79
|
-
|
|
79
|
+
const ret = exports.defaultBaseUrl.publicIps = lodash_1.default.uniq(ips.flat());
|
|
80
|
+
if (!ret.length) // don't keep empty results for long
|
|
81
|
+
setTimeout(() => exports.getPublicIps.clearRetain(), 5_000);
|
|
82
|
+
return ret;
|
|
80
83
|
}, { retain: 10 * cross_1.MINUTE });
|
|
81
84
|
exports.getNatInfo = (0, debounceAsync_1.debounceAsync)(async () => {
|
|
82
85
|
const upnp = await exports.upnpEnabled.getWhenReady() ? getUpnpClient() : null;
|
package/src/perm.js
CHANGED
|
@@ -81,7 +81,7 @@ async function updateAccount(account, change) {
|
|
|
81
81
|
const u = normalizeUsername(change.username || '');
|
|
82
82
|
if (u && u !== usernameWas && getAccount(u))
|
|
83
83
|
throw "username already exists";
|
|
84
|
-
Object.assign(account,
|
|
84
|
+
Object.assign(account, lodash_1.default.mapValues(change, x => x || undefined));
|
|
85
85
|
}
|
|
86
86
|
for (const [k, v] of (0, misc_1.typedEntries)(account))
|
|
87
87
|
if (!v)
|
package/src/plugins.js
CHANGED
|
@@ -158,7 +158,7 @@ async function initPlugin(pl, morePassedToInit) {
|
|
|
158
158
|
const controlledEvents = Object.create(events_1.default, (0, misc_1.objFromKeys)(['on', 'once', 'multi'], k => ({
|
|
159
159
|
value() {
|
|
160
160
|
if (k === 'multi')
|
|
161
|
-
arguments[0] =
|
|
161
|
+
arguments[0] = lodash_1.default.mapValues(arguments[0], trap);
|
|
162
162
|
else
|
|
163
163
|
arguments[1] = trap(arguments[1]);
|
|
164
164
|
const ret = events_1.default[k](...arguments);
|
|
@@ -582,7 +582,7 @@ function watchPlugin(id, path) {
|
|
|
582
582
|
getConfig(cfgKey) {
|
|
583
583
|
const cur = exports.pluginsConfig.get()?.[id];
|
|
584
584
|
return cfgKey ? cur?.[cfgKey] ?? pluginData.config?.[cfgKey]?.defaultValue
|
|
585
|
-
: lodash_1.default.defaults(cur,
|
|
585
|
+
: lodash_1.default.defaults(cur, lodash_1.default.mapValues(pluginData.config, x => x.defaultValue));
|
|
586
586
|
},
|
|
587
587
|
setConfig: (cfgKey, value) => setPluginConfig(id, { [cfgKey]: value }),
|
|
588
588
|
subscribeConfig(cfgKey, cb) {
|
package/src/roots.js
CHANGED
|
@@ -32,7 +32,7 @@ const rootsMiddleware = (ctx, next) => (() => {
|
|
|
32
32
|
}
|
|
33
33
|
if (lodash_1.default.isEmpty(exports.roots.get()))
|
|
34
34
|
return;
|
|
35
|
-
const root = ctx.state.root = exports.roots.compiled()
|
|
35
|
+
const root = ctx.state.root = exports.roots.compiled()(ctx.host);
|
|
36
36
|
if (!ctx.state.skipFilters && forceAddress.get()
|
|
37
37
|
&& root === undefined && !(0, misc_1.isLocalHost)(ctx) && ctx.host !== listen_1.baseUrl.compiled())
|
|
38
38
|
return (0, connections_1.disconnect)(ctx, forceAddress.key()); // returning truthy will not call next
|
|
@@ -46,7 +46,7 @@ const rootsMiddleware = (ctx, next) => (() => {
|
|
|
46
46
|
return;
|
|
47
47
|
for (const [k, v] of Object.entries(params))
|
|
48
48
|
if (k.startsWith('uri'))
|
|
49
|
-
params[k] = Array.isArray(v) ? v.map(cb) : cb(v);
|
|
49
|
+
params[k] = Array.isArray(v) ? v.map(cb) : lodash_1.default.isString(v) ? cb(v) : v;
|
|
50
50
|
}
|
|
51
51
|
})() || next();
|
|
52
52
|
exports.rootsMiddleware = rootsMiddleware;
|
package/src/selfCheck.js
CHANGED
|
@@ -38,7 +38,8 @@ async function selfCheck(url) {
|
|
|
38
38
|
console.debug(svc);
|
|
39
39
|
body = applySymbols(body);
|
|
40
40
|
serviceUrl = applySymbols(serviceUrl);
|
|
41
|
-
const
|
|
41
|
+
const timeout = 8_000;
|
|
42
|
+
const res = await (0, cross_1.haveTimeout)(timeout, (0, util_http_1.httpString)(serviceUrl, { family, timeout, ...rest, body }));
|
|
42
43
|
const success = new RegExp(regexpSuccess).test(res);
|
|
43
44
|
const failure = new RegExp(regexpFailure).test(res);
|
|
44
45
|
if (success === failure)
|
|
@@ -21,6 +21,7 @@ const serveGuiFiles_1 = require("./serveGuiFiles");
|
|
|
21
21
|
const koa_mount_1 = __importDefault(require("koa-mount"));
|
|
22
22
|
const listen_1 = require("./listen");
|
|
23
23
|
const misc_1 = require("./misc");
|
|
24
|
+
const roots_1 = require("./roots");
|
|
24
25
|
const xxhashjs_1 = __importDefault(require("xxhashjs"));
|
|
25
26
|
const fs_1 = __importDefault(require("fs"));
|
|
26
27
|
const promises_1 = require("fs/promises");
|
|
@@ -119,11 +120,10 @@ const serveSharedFiles = async (ctx, next) => {
|
|
|
119
120
|
return ctx.status = cross_const_1.HTTP_SERVER_ERROR;
|
|
120
121
|
}
|
|
121
122
|
}
|
|
122
|
-
if (
|
|
123
|
-
const found = await (0, vfs_1.
|
|
124
|
-
if (found && /\.html?/i.test(
|
|
123
|
+
if (path.endsWith('/') && !get) { // final slash needed on browsers to make resource urls working with html pages
|
|
124
|
+
const found = await (0, vfs_1.getDefaultFile)(node, ctx);
|
|
125
|
+
if (found && /\.html?/i.test((0, vfs_1.getNodeName)(node = found)))
|
|
125
126
|
ctx.state.considerAsGui = true;
|
|
126
|
-
node = found ?? node;
|
|
127
127
|
}
|
|
128
128
|
if (get === 'icon')
|
|
129
129
|
return (0, serveFile_1.serveFile)(ctx, node.icon || '|'); // pipe to cause not-found
|
|
@@ -160,9 +160,19 @@ async function sendFolderList(node, ctx) {
|
|
|
160
160
|
ctx.type = 'text';
|
|
161
161
|
if (prepend === undefined || prepend === '*') { // * = force auto-detection even if we have baseUrl set
|
|
162
162
|
const { URL } = ctx;
|
|
163
|
-
const
|
|
164
|
-
|
|
165
|
-
|
|
163
|
+
const requestBase = URL.protocol + '//' + URL.host + ctx.state.revProxyPath;
|
|
164
|
+
const configuredBaseUrl = prepend === undefined && listen_1.baseUrl.get();
|
|
165
|
+
const configuredRoot = configuredBaseUrl && roots_1.roots.compiled()(listen_1.baseUrl.compiled() || '');
|
|
166
|
+
const pathInConfiguredRoot = configuredRoot && (configuredRoot === '/' ? ctx.path
|
|
167
|
+
: ctx.path.startsWith(configuredRoot) ? ctx.path.slice(configuredRoot.length - 1)
|
|
168
|
+
: ctx.path === configuredRoot.slice(0, -1) ? '/'
|
|
169
|
+
: false);
|
|
170
|
+
// base_url may expose a host-rooted home; use it only for VFS paths inside that host root
|
|
171
|
+
const [base, path] = !configuredBaseUrl ? [requestBase, ctx.path]
|
|
172
|
+
: pathInConfiguredRoot === false ? [requestBase, ctx.path]
|
|
173
|
+
: [configuredBaseUrl, pathInConfiguredRoot || ctx.path];
|
|
174
|
+
// redo the encoding our way, keeping unicode chars unchanged. decode each segment separately because decodeURI preserves reserved escapes like %3A, which pathEncode would double-encode
|
|
175
|
+
prepend = base + (0, misc_1.pathDecodeSegments)(path, misc_1.pathEncode);
|
|
166
176
|
}
|
|
167
177
|
const walker = (0, vfs_1.walkNode)(node, { ctx, depth: depth === '*' ? Infinity : Number(depth), parallelizeRecursion: false }); // parallelization produces out-of-order results, and we don't want it like that here
|
|
168
178
|
ctx.body = (0, misc_1.asyncGeneratorToReadable)((0, misc_1.filterMapGenerator)(walker, async (el) => {
|
package/src/serveGuiFiles.js
CHANGED
|
@@ -39,7 +39,7 @@ function serveStatic(uri) {
|
|
|
39
39
|
return ctx.status = const_1.HTTP_METHOD_NOT_ALLOWED;
|
|
40
40
|
const serveApp = shouldServeApp(ctx);
|
|
41
41
|
const fullPath = (0, path_1.join)(__dirname, '..', folder, serveApp ? '/index.html' : ctx.path);
|
|
42
|
-
const content = await (0, misc_1.parseFile)(fullPath, raw => serveApp || !raw.length ? raw : adjustBundlerLinks(ctx, uri, raw))
|
|
42
|
+
const content = await (0, misc_1.parseFile)(fullPath, raw => serveApp || !raw.length ? raw : adjustBundlerLinks(ctx, uri, raw), 1000)
|
|
43
43
|
.catch(e => {
|
|
44
44
|
if (e?.code !== 'ENOENT') // not supposed to happen, and yet a user reported a strange behavior
|
|
45
45
|
console.error(`serveStatic/parseFile: ${String(e)}`);
|
|
@@ -118,7 +118,7 @@ async function treatIndex(ctx, filesUri, body) {
|
|
|
118
118
|
${(0, customHtml_1.getSection)('htmlHead')}`}
|
|
119
119
|
`;
|
|
120
120
|
function iconsToObj(icons, pre = '') {
|
|
121
|
-
return icons &&
|
|
121
|
+
return icons && lodash_1.default.mapValues(icons, (v, k) => ctx.state.revProxyPath + const_1.ICONS_URI + pre + k);
|
|
122
122
|
}
|
|
123
123
|
if (isBody && isOpen)
|
|
124
124
|
return `${all}
|
|
@@ -155,7 +155,7 @@ async function treatIndex(ctx, filesUri, body) {
|
|
|
155
155
|
v = ctx.state.revProxyPath + v;
|
|
156
156
|
}
|
|
157
157
|
else if (type === 'array' && Array.isArray(v))
|
|
158
|
-
v = v.map(x =>
|
|
158
|
+
v = v.map(x => lodash_1.default.mapValues(x, (xv, xk) => adjustValueByConfig(xv, cfg.fields[xk])));
|
|
159
159
|
return v;
|
|
160
160
|
}
|
|
161
161
|
}
|
package/src/update.js
CHANGED
|
@@ -128,9 +128,7 @@ async function update(tagOrUrl = '') {
|
|
|
128
128
|
throw "No update has been found";
|
|
129
129
|
const plat = '-' + (0, misc_1.xlate)(process.platform, { win32: 'windows', darwin: 'mac' });
|
|
130
130
|
const assetSearch = `${plat}-${process.arch}`;
|
|
131
|
-
const
|
|
132
|
-
const asset = update.assets.find((x) => x.name.includes(assetSearch) && x.name.endsWith('.zip'))
|
|
133
|
-
|| update.assets.find((x) => x.name.endsWith(legacyAssetSearch));
|
|
131
|
+
const asset = update.assets.find((x) => x.name.includes(assetSearch) && x.name.endsWith('.zip'));
|
|
134
132
|
if (!asset)
|
|
135
133
|
throw `Asset not found: ${assetSearch}`;
|
|
136
134
|
url = asset.browser_download_url;
|
package/src/util-files.js
CHANGED
|
@@ -182,19 +182,24 @@ function exists(path) {
|
|
|
182
182
|
}
|
|
183
183
|
// parse a file, caching unless timestamp has changed
|
|
184
184
|
exports.parseFileCache = new Map();
|
|
185
|
-
async function loadFileCached(path, loader) {
|
|
185
|
+
async function loadFileCached(path, loader, minInterval = 0) {
|
|
186
186
|
const cached = exports.parseFileCache.get(path);
|
|
187
|
+
const now = Date.now();
|
|
188
|
+
if (cached && now - cached.lastCheck < minInterval)
|
|
189
|
+
return cached.parsed;
|
|
187
190
|
const ts = await statWithTimeout(path).then(x => x.mtime, e => {
|
|
188
191
|
if (e?.message !== 'timeout')
|
|
189
192
|
throw e;
|
|
190
193
|
return cached?.ts || new Date(0); // on timeout (e.g. thread pool saturated), serve cache if any, or attempt the loader
|
|
191
194
|
});
|
|
195
|
+
if (cached)
|
|
196
|
+
cached.lastCheck = now;
|
|
192
197
|
if (cached && Number(ts) === Number(cached.ts))
|
|
193
198
|
return cached.parsed;
|
|
194
199
|
const parsed = loader(path);
|
|
195
|
-
exports.parseFileCache.set(path, { ts, parsed });
|
|
200
|
+
exports.parseFileCache.set(path, { ts, parsed, lastCheck: now });
|
|
196
201
|
return parsed;
|
|
197
202
|
}
|
|
198
|
-
async function parseFile(path, parse) {
|
|
199
|
-
return loadFileCached(path, () => (0, promises_1.readFile)(path).then(parse));
|
|
203
|
+
async function parseFile(path, parse, skipStatIfFresherThan = 0) {
|
|
204
|
+
return loadFileCached(path, () => (0, promises_1.readFile)(path).then(parse), skipStatIfFresherThan);
|
|
200
205
|
}
|
package/src/util-http.js
CHANGED
|
@@ -137,6 +137,8 @@ function httpStream(url, { body, proxy, jar, noRedirect, httpThrow = true, ...op
|
|
|
137
137
|
e.cause ??= req; // enrich the error
|
|
138
138
|
reject(e);
|
|
139
139
|
});
|
|
140
|
+
if (options.timeout) // node only emits the timeout event, so destroy the request to unblock callers waiting for the body
|
|
141
|
+
req.setTimeout(options.timeout, () => req.destroy(Object.assign(Error('timeout'), { code: 'ETIMEDOUT' })));
|
|
140
142
|
if (body && body instanceof node_stream_1.Readable)
|
|
141
143
|
body.pipe(req).on('end', () => req.end());
|
|
142
144
|
else
|