hfs 3.2.2 → 3.3.0-alpha1
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-BDoqNBMg.js → af-CevhRske.js} +1 -1
- package/admin/assets/{am-DXBvKJ3X.js → am-4ouVTwuo.js} +1 -1
- package/admin/assets/{ar-BJIoxKTh.js → ar-71cK8dbb.js} +1 -1
- package/admin/assets/{ar-dz-C9l3uc_3.js → ar-dz-CO7oh1gG.js} +1 -1
- package/admin/assets/{ar-iq-DKH8u2lX.js → ar-iq-CjG6db8J.js} +1 -1
- package/admin/assets/{ar-kw-D3gJalQp.js → ar-kw-DMsdR225.js} +1 -1
- package/admin/assets/{ar-ly-BZJzWFeR.js → ar-ly-ZhxIwUOA.js} +1 -1
- package/admin/assets/{ar-ma-DabiOCr0.js → ar-ma-Bo3Z1RyK.js} +1 -1
- package/admin/assets/{ar-sa-C7suzSCY.js → ar-sa-Db44biae.js} +1 -1
- package/admin/assets/{ar-tn-BvBucEx0.js → ar-tn-DNWRixE0.js} +1 -1
- package/admin/assets/{az-CPUppH2M.js → az-B6LcmVzB.js} +1 -1
- package/admin/assets/{be-vzvhX2dl.js → be-Dp0KhO5x.js} +1 -1
- package/admin/assets/{bg-09Xbj_Y3.js → bg-TsOJJvBm.js} +1 -1
- package/admin/assets/{bi-CvhiuiE9.js → bi-DstczOGo.js} +1 -1
- package/admin/assets/{bm-DtJAZAkZ.js → bm-Bf09NN15.js} +1 -1
- package/admin/assets/{bn-Dnzh2x6I.js → bn-Da3tHLgx.js} +1 -1
- package/admin/assets/{bn-bd-xEy4797g.js → bn-bd-CeSalisC.js} +1 -1
- package/admin/assets/{bo-vuPmptG3.js → bo-Ctxe0IKQ.js} +1 -1
- package/admin/assets/{br-C9xH4OD3.js → br-Y3dPq3Tj.js} +1 -1
- package/admin/assets/{bs-BB_uCUp1.js → bs-BbAlSeZx.js} +1 -1
- package/admin/assets/{ca-D7LKaf98.js → ca-8-Nn97gi.js} +1 -1
- package/admin/assets/{cs-DMFTNdNC.js → cs-BD5ArFNW.js} +1 -1
- package/admin/assets/{cv-DNUjJwnO.js → cv-CdMFVXGr.js} +1 -1
- package/admin/assets/{cy-qmAOTPwC.js → cy-B3QEYlIT.js} +1 -1
- package/admin/assets/{da-DDX54iM9.js → da-Bju2YQ9G.js} +1 -1
- package/admin/assets/{de-BBmlsfbM.js → de-DdzCTquR.js} +1 -1
- package/admin/assets/{de-at-CuMZqYfu.js → de-at-DgyKCkPl.js} +1 -1
- package/admin/assets/{de-ch-s8OXKb-5.js → de-ch-Dk9qkzvn.js} +1 -1
- package/admin/assets/{dist-CHP3cW_n.js → dist-KY_8edrw.js} +1 -1
- package/admin/assets/{dv-Cjs3QDBN.js → dv-C5KWK8o4.js} +1 -1
- package/admin/assets/{el-BsAOrUDP.js → el-BDL_lPlM.js} +1 -1
- package/admin/assets/{en-DQ460ET7.js → en-Ds3H1Wec.js} +1 -1
- package/admin/assets/{en-au-8tMHM2OT.js → en-au-CgltfNUb.js} +1 -1
- package/admin/assets/{en-ca-c9jM4M_8.js → en-ca-DEsKzbWO.js} +1 -1
- package/admin/assets/{en-in-D2ULzXMp.js → en-gb-21AYC3Ik.js} +1 -1
- package/admin/assets/{en-ie-D4_e3kV6.js → en-ie-aN3g-yqK.js} +1 -1
- package/admin/assets/{en-il-BoaZOven.js → en-il-CZl2d7NK.js} +1 -1
- package/admin/assets/{en-tt-Cn72OR4N.js → en-in-C7Kv4c3w.js} +1 -1
- package/admin/assets/{en-nz-grrmB3dg.js → en-nz-DA_sIaGR.js} +1 -1
- package/admin/assets/{en-sg-Cmqxpoir.js → en-sg-CP5PJpZf.js} +1 -1
- package/admin/assets/{en-gb-unFIXx9f.js → en-tt-B5vDLhL7.js} +1 -1
- package/admin/assets/{eo-Cqxmc0TT.js → eo-VE1KDD1u.js} +1 -1
- package/admin/assets/{es-CkjKdFZ3.js → es-SUzi5oTU.js} +1 -1
- package/admin/assets/{es-do-CTcTshup.js → es-do-Iri0eWm8.js} +1 -1
- package/admin/assets/{es-mx-DMXgMMxs.js → es-mx-DwneKeOc.js} +1 -1
- package/admin/assets/{es-pr-DNCV91L3.js → es-pr-C4zPUdA1.js} +1 -1
- package/admin/assets/{es-us-C7o2mlIH.js → es-us-BPS67etc.js} +1 -1
- package/admin/assets/{esm-DvUbRuYx.js → esm-CyNQ7bBW.js} +1 -1
- package/admin/assets/{et-DaPpH-gK.js → et-Ciqbfae0.js} +1 -1
- package/admin/assets/{eu-DfKyFruu.js → eu-CGhOwQpe.js} +1 -1
- package/admin/assets/{fa-xWrBjePb.js → fa-BWZiAv_E.js} +1 -1
- package/admin/assets/{fi-BOWDMsDS.js → fi-DJmgkVsJ.js} +1 -1
- package/admin/assets/{fo-B3FupW8R.js → fo-DVez5N-g.js} +1 -1
- package/admin/assets/{fr-DZBDBkwp.js → fr-D206itJo.js} +1 -1
- package/admin/assets/{fr-ca-CRePPJJ5.js → fr-ca-C6JiEHfi.js} +1 -1
- package/admin/assets/{fr-ch-pFKvYU8D.js → fr-ch-DUTEuSU_.js} +1 -1
- package/admin/assets/{fy-0k7RzHUH.js → fy-C_r06zH-.js} +1 -1
- package/admin/assets/{ga-D31rB2ys.js → ga-CrdQqAN5.js} +1 -1
- package/admin/assets/{gd-CbgY-FFE.js → gd-iWHfhKAH.js} +1 -1
- package/admin/assets/{gl-DrtvS9gt.js → gl-zgfy-pQ0.js} +1 -1
- package/admin/assets/{gom-latn-Dkke3GyE.js → gom-latn-CMZ7Vj7V.js} +1 -1
- package/admin/assets/{gu-BlEipYhR.js → gu-DNqIMjZc.js} +1 -1
- package/admin/assets/{he-BrHT6OLB.js → he-OJBbYFzL.js} +1 -1
- package/admin/assets/{hi-Bx0zlAgr.js → hi-ByrMhKpX.js} +1 -1
- package/admin/assets/{hr-CoFYGbr6.js → hr-CfRZXzCu.js} +1 -1
- package/admin/assets/{ht-Df5xKo4a.js → ht-DDrMtLzJ.js} +1 -1
- package/admin/assets/{hu-BFTN3q6-.js → hu-Ckeq3R6_.js} +1 -1
- package/admin/assets/{hy-am-DuZTJQed.js → hy-am-OpZy7Nvg.js} +1 -1
- package/admin/assets/{id-DBZ5nPkf.js → id-BE8-Ys79.js} +1 -1
- package/admin/assets/{index-BGIe1xaR.js → index-34fRsXFH.js} +128 -123
- package/admin/assets/{is-CmeHm47B.js → is-BOxJcH5N.js} +1 -1
- package/admin/assets/{it-Cv3fjOA9.js → it-BFlrIXvP.js} +1 -1
- package/admin/assets/{it-ch-DCTyHA3T.js → it-ch-ILAp-lZZ.js} +1 -1
- package/admin/assets/{ja-DHNVXAM5.js → ja-CbbYAYyn.js} +1 -1
- package/admin/assets/{jv-BdNIxhWv.js → jv-CuLkRQCH.js} +1 -1
- package/admin/assets/{ka-D-joD0QB.js → ka-BFeRDN-I.js} +1 -1
- package/admin/assets/{kk-8wRNNjxz.js → kk-CMvQxaPk.js} +1 -1
- package/admin/assets/{km-dUWOg1uK.js → km-BdQbDUNF.js} +1 -1
- package/admin/assets/{kn-CwQbzel0.js → kn-CE5AS6Nx.js} +1 -1
- package/admin/assets/{ko-D_gn9Sfw.js → ko-Ctl1mHOt.js} +1 -1
- package/admin/assets/{ku-Dh3MRML3.js → ku-Blbo7opr.js} +1 -1
- package/admin/assets/{ky-CWYuRvil.js → ky-iCha_cZX.js} +1 -1
- package/admin/assets/{lb-Cc2xsSIx.js → lb-BaP0S6h1.js} +1 -1
- package/admin/assets/{lo-CkwNFDBz.js → lo-B_Q4zUMa.js} +1 -1
- package/admin/assets/{lt-HWyFDLXu.js → lt-BOaP1KGX.js} +1 -1
- package/admin/assets/{lv-C7Zx3OLO.js → lv-S1XMSUX1.js} +1 -1
- package/admin/assets/{me-DH3AqnfF.js → me-ChOLVJHC.js} +1 -1
- package/admin/assets/{mi-DMmUry6m.js → mi-CnoIUOGl.js} +1 -1
- package/admin/assets/{mk-D6MNZrzX.js → mk-BoEaxVXE.js} +1 -1
- package/admin/assets/{ml-B_ur8Zqf.js → ml-BuvmVn6P.js} +1 -1
- package/admin/assets/{mn-DeL8Ry7x.js → mn-2bvGd1HI.js} +1 -1
- package/admin/assets/{mr-C01qRXFL.js → mr-BAoAWQmy.js} +1 -1
- package/admin/assets/{ms-DFUZJzOm.js → ms-C2eT9r-f.js} +1 -1
- package/admin/assets/{ms-my-CUYHkebs.js → ms-my-D2ZXdcAf.js} +1 -1
- package/admin/assets/{mt-CE8t5jSj.js → mt-BL5JZC4T.js} +1 -1
- package/admin/assets/{my-Df96A8jZ.js → my-CuJ4L0Ji.js} +1 -1
- package/admin/assets/{nb-D0837ewu.js → nb-CjE6cT80.js} +1 -1
- package/admin/assets/{ne-BdK5TR0R.js → ne-BL9I0cb1.js} +1 -1
- package/admin/assets/{nl-oYKmSma6.js → nl-Dt3mtMln.js} +1 -1
- package/admin/assets/{nl-be-BfVC0cur.js → nl-be-Am1mjZK9.js} +1 -1
- package/admin/assets/{nn-IEUPnyc8.js → nn-ChSewyYy.js} +1 -1
- package/admin/assets/{oc-lnc-g_wSIROr.js → oc-lnc-x6_ChCE9.js} +1 -1
- package/admin/assets/{pa-in-gWhcLmZE.js → pa-in-DVyN8o2P.js} +1 -1
- package/admin/assets/{pl-H3-LoxVn.js → pl-CqcuAUGV.js} +1 -1
- package/admin/assets/{pt-hiX8RqVw.js → pt-B2BPpD7K.js} +1 -1
- package/admin/assets/{pt-br-B8UcNR6_.js → pt-br-oBVRyUzP.js} +1 -1
- package/admin/assets/{rn-BKupViLr.js → rn-B3e3nkfp.js} +1 -1
- package/admin/assets/{ro-DmhgIvml.js → ro-C2tWFx0Q.js} +1 -1
- package/admin/assets/{ru-CcpO-b8g.js → ru-CCsEUnFL.js} +1 -1
- package/admin/assets/{rw-DWX_5ZK0.js → rw-CaCkWlGA.js} +1 -1
- package/admin/assets/{sd-Dat8YgLw.js → sd-90Zixcez.js} +1 -1
- package/admin/assets/{se-nTBYCwx3.js → se-DaZXpLsK.js} +1 -1
- package/admin/assets/{sha512-CZz40GwW.js → sha512-DlGRFb_l.js} +1 -1
- package/admin/assets/{si-DG88w5El.js → si-CSoBAkgi.js} +1 -1
- package/admin/assets/{sk-s5FxW36J.js → sk-pgQaqa2z.js} +1 -1
- package/admin/assets/{sl-B-HyGrpA.js → sl-Cr5kSdA7.js} +1 -1
- package/admin/assets/{sq-DPFtfEC8.js → sq-Pxi2MZOP.js} +1 -1
- package/admin/assets/{sr-bBPg1zz4.js → sr-Fdc9-lC-.js} +1 -1
- package/admin/assets/{sr-cyrl-cXkYHMcP.js → sr-cyrl-06jNWqpg.js} +1 -1
- package/admin/assets/{ss-7nSqXJN7.js → ss-BjEzSMit.js} +1 -1
- package/admin/assets/{sv-B8mG1QK4.js → sv-CqWqUpcA.js} +1 -1
- package/admin/assets/{sv-fi-CotGRXSH.js → sv-fi-CsO2xQHd.js} +1 -1
- package/admin/assets/{sw-fAxU-Uck.js → sw-xQUdgYhn.js} +1 -1
- package/admin/assets/{ta-Dl0Z1pan.js → ta-epbFrZ1M.js} +1 -1
- package/admin/assets/{te-DmcKasFF.js → te-DusTwU6q.js} +1 -1
- package/admin/assets/{tet-D6mmvYpQ.js → tet-ylc9v4gl.js} +1 -1
- package/admin/assets/{tg-39Hf2-oO.js → tg-2QfVTAJf.js} +1 -1
- package/admin/assets/{th-BSmTJSFN.js → th-QIm0Mh7I.js} +1 -1
- package/admin/assets/{tk-m85E5igC.js → tk-140KMZeW.js} +1 -1
- package/admin/assets/{tl-ph-BlYxVvNt.js → tl-ph-S7IZkbx3.js} +1 -1
- package/admin/assets/{tlh-D1POnIeE.js → tlh-C2fYb34-.js} +1 -1
- package/admin/assets/{tr-BurtfrLy.js → tr-Be-IM6i4.js} +1 -1
- package/admin/assets/{tzl-BvyD0lOf.js → tzl-DMy30zde.js} +1 -1
- package/admin/assets/{tzm-DZHS0dK6.js → tzm-Cu882cHO.js} +1 -1
- package/admin/assets/{tzm-latn-yNW7CNCH.js → tzm-latn-BYxpYHet.js} +1 -1
- package/admin/assets/{ug-cn-rxs2pf5Y.js → ug-cn-Cl8Jqk2E.js} +1 -1
- package/admin/assets/{uk-CgZM-NnA.js → uk-B-8XymtC.js} +1 -1
- package/admin/assets/{ur-YxIR8yYD.js → ur-ChGuAlvQ.js} +1 -1
- package/admin/assets/{uz-CE9QsQMG.js → uz-QbfaW7fT.js} +1 -1
- package/admin/assets/{uz-latn-DiVdSW6G.js → uz-latn-DIQivLCy.js} +1 -1
- package/admin/assets/{vi-DKARV6qf.js → vi-C2CmYlDu.js} +1 -1
- package/admin/assets/{x-pseudo-BuAdOrXQ.js → x-pseudo-BWgm1XlP.js} +1 -1
- package/admin/assets/{yo-BcB85DhC.js → yo-8bNbPP6u.js} +1 -1
- package/admin/assets/{zh-DLmxfxzl.js → zh-CeNSRZ4r.js} +1 -1
- package/admin/assets/{zh-cn-ILRADZJi.js → zh-cn-BlbF5C_X.js} +1 -1
- package/admin/assets/{zh-hk-fT7fQk9_.js → zh-hk-kg1c_VSK.js} +1 -1
- package/admin/assets/{zh-tw-NqRXGP6v.js → zh-tw-BCl4UsPA.js} +1 -1
- package/admin/index.html +1 -1
- package/frontend/assets/{dist-legacy-BPToXNXu.js → dist-legacy-DYE1EOcc.js} +1 -1
- package/frontend/assets/index-legacy-Br-Lpsst.js +1 -0
- package/frontend/assets/{lib-legacy-CBk-_G90.js → lib-legacy-C4Gw_0kX.js} +1 -1
- package/frontend/assets/{sha512-legacy-CWrR2rkQ.js → sha512-legacy-D3RbPSpl.js} +1 -1
- package/frontend/index.html +1 -1
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
- package/src/AsapStream.js +18 -14
- package/src/SendList.js +42 -16
- package/src/ThrottledStream.js +3 -3
- package/src/acme.js +23 -10
- package/src/adminApis.js +7 -6
- package/src/api.accounts.js +11 -7
- package/src/api.auth.js +8 -5
- package/src/api.cert.js +1 -1
- package/src/api.get_file_list.js +4 -3
- package/src/api.lang.js +3 -2
- package/src/api.monitor.js +1 -1
- package/src/api.net.js +3 -1
- package/src/api.plugins.js +2 -0
- package/src/api.vfs.js +5 -4
- package/src/apiMiddleware.js +5 -1
- package/src/basicWeb.js +1 -1
- package/src/block.js +1 -1
- package/src/commands.js +6 -5
- package/src/comments.js +29 -13
- package/src/config.js +7 -4
- package/src/consoleLog.js +5 -1
- package/src/cross-const.js +27 -1
- package/src/cross.js +32 -33
- package/src/ddns.js +1 -4
- package/src/events.js +1 -4
- package/src/fileAttr.js +1 -1
- package/src/frontEndApis.js +7 -3
- package/src/geo.js +8 -2
- package/src/github.js +10 -9
- package/src/index.js +1 -1
- package/src/ips.js +24 -3
- package/src/langs/hfs-lang-ar.json +1 -0
- package/src/langs/hfs-lang-bg.json +1 -0
- package/src/langs/hfs-lang-de.json +1 -0
- package/src/langs/hfs-lang-el.json +1 -0
- package/src/langs/hfs-lang-es.json +1 -0
- package/src/langs/hfs-lang-fi.json +1 -0
- package/src/langs/hfs-lang-fr.json +1 -0
- package/src/langs/hfs-lang-hu.json +1 -0
- package/src/langs/hfs-lang-it.json +1 -0
- package/src/langs/hfs-lang-ja.json +1 -0
- package/src/langs/hfs-lang-ko.json +1 -0
- package/src/langs/hfs-lang-lt.json +1 -0
- package/src/langs/hfs-lang-ms.json +1 -0
- package/src/langs/hfs-lang-nl.json +1 -0
- package/src/langs/hfs-lang-pl.json +1 -0
- package/src/langs/hfs-lang-pt-br.json +1 -0
- package/src/langs/hfs-lang-ro.json +1 -0
- package/src/langs/hfs-lang-ru.json +1 -0
- package/src/langs/hfs-lang-sr-latn.json +1 -0
- package/src/langs/hfs-lang-sr.json +1 -0
- package/src/langs/hfs-lang-th.json +1 -0
- package/src/langs/hfs-lang-tr.json +1 -0
- package/src/langs/hfs-lang-uk.json +1 -0
- package/src/langs/hfs-lang-vi.json +1 -0
- package/src/langs/hfs-lang-zh-tw.json +1 -0
- package/src/langs/hfs-lang-zh.json +1 -0
- package/src/listen.js +17 -13
- package/src/log.js +11 -2
- package/src/middlewares.js +4 -4
- package/src/misc.js +3 -3
- package/src/multipartUpload.js +1 -2
- package/src/nat.js +1 -1
- package/src/outboundProxy.js +7 -0
- package/src/perm.js +5 -3
- package/src/plugins.js +58 -36
- package/src/selfCheck.js +4 -3
- package/src/serveFile.js +3 -3
- package/src/serveGuiAndSharedFiles.js +7 -6
- package/src/serveGuiFiles.js +13 -1
- package/src/serverCode.js +27 -0
- package/src/throttler.js +3 -2
- package/src/update.js +9 -3
- package/src/upload.js +29 -11
- package/src/uploadOwners.js +95 -0
- package/src/util-files.js +4 -2
- package/src/util-http.js +3 -1
- package/src/vfs.js +28 -24
- package/src/walkDir.js +24 -11
- package/src/watchLoad.js +13 -2
- package/src/webdav.js +4 -2
- package/src/zip.js +1 -1
- package/frontend/assets/index-legacy-CmusDwvl.js +0 -1
package/src/outboundProxy.js
CHANGED
|
@@ -5,6 +5,13 @@ const util_http_1 = require("./util-http");
|
|
|
5
5
|
const util_os_1 = require("./util-os");
|
|
6
6
|
const const_1 = require("./const");
|
|
7
7
|
const cross_1 = require("./cross");
|
|
8
|
+
const node_net_1 = require("node:net");
|
|
9
|
+
(0, config_1.defineConfig)(cross_1.CFG.outbound_interface, '', v => {
|
|
10
|
+
if (!v || (0, node_net_1.isIP)(v))
|
|
11
|
+
return util_http_1.httpStream.defaultLocalAddress = v || undefined;
|
|
12
|
+
console.warn("Invalid outbound interface IP", v);
|
|
13
|
+
util_http_1.httpStream.defaultLocalAddress = undefined;
|
|
14
|
+
});
|
|
8
15
|
const outboundProxy = (0, config_1.defineConfig)(cross_1.CFG.outbound_proxy, '', v => {
|
|
9
16
|
util_http_1.httpStream.defaultProxy = undefined;
|
|
10
17
|
if (!v)
|
package/src/perm.js
CHANGED
|
@@ -59,7 +59,7 @@ function getAccount(username, normalize = true) {
|
|
|
59
59
|
function saveSrpInfo(account, salt, verifier) {
|
|
60
60
|
account.srp = String(salt) + '|' + String(verifier);
|
|
61
61
|
}
|
|
62
|
-
const createAdminConfig = (0, config_1.defineConfig)('create-admin', '');
|
|
62
|
+
const createAdminConfig = (0, config_1.defineConfig)(misc_1.CFG['create-admin'], '');
|
|
63
63
|
createAdminConfig.sub(v => {
|
|
64
64
|
if (!v)
|
|
65
65
|
return;
|
|
@@ -79,7 +79,9 @@ async function updateAccount(account, change) {
|
|
|
79
79
|
await change?.(account);
|
|
80
80
|
else {
|
|
81
81
|
const u = normalizeUsername(change.username || '');
|
|
82
|
-
if (u
|
|
82
|
+
if (!u)
|
|
83
|
+
delete change.username;
|
|
84
|
+
else if (u !== usernameWas && getAccount(u))
|
|
83
85
|
throw "username already exists";
|
|
84
86
|
Object.assign(account, lodash_1.default.mapValues(change, x => x || undefined));
|
|
85
87
|
}
|
|
@@ -111,7 +113,7 @@ async function updateAccount(account, change) {
|
|
|
111
113
|
saveAccountsAsap();
|
|
112
114
|
}
|
|
113
115
|
const saveAccountsAsap = config_1.saveConfigAsap;
|
|
114
|
-
exports.accounts = (0, config_1.defineConfig)(
|
|
116
|
+
exports.accounts = (0, config_1.defineConfig)(misc_1.CFG.accounts, {});
|
|
115
117
|
exports.accounts.sub(lodash_1.default.debounce(obj => {
|
|
116
118
|
// consider some validation here, in case of manual edit of the config
|
|
117
119
|
lodash_1.default.each(obj, (rec, k) => {
|
package/src/plugins.js
CHANGED
|
@@ -80,6 +80,7 @@ const perm_1 = require("./perm");
|
|
|
80
80
|
const auth_1 = require("./auth");
|
|
81
81
|
const icons_1 = require("./icons");
|
|
82
82
|
const listen_1 = require("./listen");
|
|
83
|
+
const serverCode_1 = require("./serverCode");
|
|
83
84
|
exports.PATH = 'plugins';
|
|
84
85
|
exports.DISABLING_SUFFIX = '-disabled';
|
|
85
86
|
exports.DELETE_ME_SUFFIX = '-delete_me' + exports.DISABLING_SUFFIX;
|
|
@@ -121,7 +122,7 @@ async function startPlugin(id) {
|
|
|
121
122
|
}
|
|
122
123
|
async function waitRunning(id, state = true) {
|
|
123
124
|
while (isPluginRunning(id) !== state) {
|
|
124
|
-
await (0, misc_1.wait)(
|
|
125
|
+
await (0, misc_1.wait)(50);
|
|
125
126
|
const error = getError(id);
|
|
126
127
|
if (error)
|
|
127
128
|
throw Error(error);
|
|
@@ -152,9 +153,10 @@ function findPluginByRepo(repo) {
|
|
|
152
153
|
function getPluginConfigFields(id) {
|
|
153
154
|
return plugins.get(id)?.getData().config;
|
|
154
155
|
}
|
|
155
|
-
async function initPlugin(pl, morePassedToInit) {
|
|
156
|
+
async function initPlugin(pl, morePassedToInit, onInitError) {
|
|
156
157
|
const undoEvents = [];
|
|
157
158
|
const timeouts = [];
|
|
159
|
+
let unload = pl.unload;
|
|
158
160
|
const controlledEvents = Object.create(events_1.default, (0, misc_1.objFromKeys)(['on', 'once', 'multi'], k => ({
|
|
159
161
|
value() {
|
|
160
162
|
if (k === 'multi')
|
|
@@ -183,7 +185,7 @@ async function initPlugin(pl, morePassedToInit) {
|
|
|
183
185
|
}
|
|
184
186
|
}
|
|
185
187
|
})));
|
|
186
|
-
const res = await pl.init?.({
|
|
188
|
+
const res = await (0, misc_1.callAsPromise)(() => pl.init?.({
|
|
187
189
|
Const, require,
|
|
188
190
|
// intercept all subscriptions, so to be able to undo them on unload
|
|
189
191
|
events: controlledEvents,
|
|
@@ -220,18 +222,25 @@ async function initPlugin(pl, morePassedToInit) {
|
|
|
220
222
|
customApiCall, notifyClient: frontEndApis_1.notifyClient, addBlock: block_1.addBlock, ctxBelongsTo: perm_1.ctxBelongsTo, getConnections: connections_1.getConnections, normalizeFilename: vfs_1.normalizeFilename,
|
|
221
223
|
getCurrentUsername: auth_1.getCurrentUsername, getAccount: perm_1.getAccount, getUsernames: perm_1.getUsernames, addAccount: perm_1.addAccount, delAccount: perm_1.delAccount, updateAccount: perm_1.updateAccount, renameAccount: perm_1.renameAccount,
|
|
222
224
|
...morePassedToInit
|
|
225
|
+
})).catch(async (e) => {
|
|
226
|
+
// failed initialization must not leave any partially allocated plugin resources active
|
|
227
|
+
await cleanup().catch(console.error);
|
|
228
|
+
await onInitError?.();
|
|
229
|
+
throw e;
|
|
223
230
|
});
|
|
224
231
|
Object.assign(pl, typeof res === 'function' ? { unload: res } : res);
|
|
225
|
-
|
|
232
|
+
unload = pl.unload;
|
|
233
|
+
(0, misc_1.patchKey)(pl, 'unload', () => cleanup);
|
|
234
|
+
events_1.default.emit('pluginInitialized', pl);
|
|
235
|
+
return pl;
|
|
236
|
+
async function cleanup() {
|
|
226
237
|
for (const x of timeouts)
|
|
227
238
|
clearTimeout(x);
|
|
228
239
|
for (const cb of undoEvents)
|
|
229
240
|
cb();
|
|
230
|
-
if (typeof
|
|
231
|
-
return
|
|
232
|
-
}
|
|
233
|
-
events_1.default.emit('pluginInitialized', pl);
|
|
234
|
-
return pl;
|
|
241
|
+
if (typeof unload === 'function')
|
|
242
|
+
return unload();
|
|
243
|
+
}
|
|
235
244
|
}
|
|
236
245
|
const pluginsMiddleware = async (ctx, next) => {
|
|
237
246
|
const after = {};
|
|
@@ -286,7 +295,7 @@ const pluginsMiddleware = async (ctx, next) => {
|
|
|
286
295
|
printError(id, e);
|
|
287
296
|
}
|
|
288
297
|
function printChange(id) {
|
|
289
|
-
if (id
|
|
298
|
+
if (id.startsWith(exports.SERVER_CODE_ID) || (lastStatus === ctx.status && lastBody === ctx.body))
|
|
290
299
|
return;
|
|
291
300
|
console.debug("Plugin changed response:", id);
|
|
292
301
|
lastStatus = ctx.status;
|
|
@@ -382,34 +391,44 @@ class Plugin {
|
|
|
382
391
|
console.log('Error unloading plugin', id, String(e));
|
|
383
392
|
}
|
|
384
393
|
await this.onUnload();
|
|
385
|
-
if (!reloading && id
|
|
394
|
+
if (!reloading && !id.startsWith(exports.SERVER_CODE_ID)) // we already printed 'reloading'
|
|
386
395
|
console.log('Unloaded plugin', id);
|
|
387
396
|
if (this.data)
|
|
388
397
|
this.data.unload = undefined;
|
|
389
398
|
}
|
|
390
399
|
}
|
|
391
400
|
exports.Plugin = Plugin;
|
|
392
|
-
exports.SERVER_CODE_ID = '
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
401
|
+
exports.SERVER_CODE_ID = '/'; // a name that will surely be not found among plugin folders
|
|
402
|
+
let unloadServerCode = lodash_1.default.noop;
|
|
403
|
+
(0, config_1.defineConfig)(misc_1.CFG.server_code, '').sub(async (text) => {
|
|
404
|
+
await unloadServerCode();
|
|
405
|
+
const asPlugins = await Promise.all((0, serverCode_1.parseServerCode)(text).map(async ({ name, code }, i) => {
|
|
406
|
+
const res = {};
|
|
407
|
+
try {
|
|
408
|
+
new Function('exports,require', code)(res, require); // parse
|
|
409
|
+
await initPlugin(res);
|
|
410
|
+
res.getCustomHtml = () => (0, misc_1.callable)(res.customHtml) || {};
|
|
411
|
+
// server-code sections can share or omit names, so include the index to keep internal plugin ids unique
|
|
412
|
+
return new Plugin(exports.SERVER_CODE_ID + i + ':' + name, '', res, lodash_1.default.noop);
|
|
413
|
+
}
|
|
414
|
+
catch (e) {
|
|
415
|
+
return console.error(name + ':', e.message || String(e));
|
|
416
|
+
}
|
|
417
|
+
}));
|
|
418
|
+
// server_code is a side-effect config, so keep its cleanup outside compiled()
|
|
419
|
+
unloadServerCode = () => Promise.all(asPlugins.map(p => {
|
|
420
|
+
try {
|
|
421
|
+
return p?.unload();
|
|
422
|
+
} // catch sync errors
|
|
423
|
+
catch (e) {
|
|
424
|
+
console.error(e);
|
|
425
|
+
}
|
|
426
|
+
})).catch(console.error);
|
|
408
427
|
});
|
|
409
428
|
function mapPlugins(cb, includeServerCode = true) {
|
|
410
429
|
let i = 0;
|
|
411
430
|
return Array.from(plugins).map(([plName, pl]) => {
|
|
412
|
-
if (!includeServerCode && plName
|
|
431
|
+
if (!includeServerCode && plName.startsWith(exports.SERVER_CODE_ID))
|
|
413
432
|
return;
|
|
414
433
|
try {
|
|
415
434
|
return cb(pl, plName, i++);
|
|
@@ -421,7 +440,7 @@ function mapPlugins(cb, includeServerCode = true) {
|
|
|
421
440
|
}
|
|
422
441
|
function firstPlugin(cb, includeServerCode = true) {
|
|
423
442
|
for (const [plName, pl] of plugins.entries()) {
|
|
424
|
-
if (!includeServerCode && plName
|
|
443
|
+
if (!includeServerCode && plName.startsWith(exports.SERVER_CODE_ID))
|
|
425
444
|
continue;
|
|
426
445
|
try {
|
|
427
446
|
const ret = cb(pl, plName);
|
|
@@ -444,10 +463,10 @@ if (!(0, fs_1.existsSync)(exports.PATH))
|
|
|
444
463
|
}
|
|
445
464
|
catch { }
|
|
446
465
|
exports.pluginsWatcher = (0, misc_1.watchDir)(exports.PATH, rescanAsap);
|
|
447
|
-
exports.enablePlugins = (0, config_1.defineConfig)(
|
|
466
|
+
exports.enablePlugins = (0, config_1.defineConfig)(misc_1.CFG.enable_plugins, ['antibrute']);
|
|
448
467
|
exports.enablePlugins.sub(rescanAsap);
|
|
449
468
|
exports.suspendPlugins = (0, config_1.defineConfig)(misc_1.CFG.suspend_plugins, false);
|
|
450
|
-
exports.pluginsConfig = (0, config_1.defineConfig)(
|
|
469
|
+
exports.pluginsConfig = (0, config_1.defineConfig)(misc_1.CFG.plugins_config, {});
|
|
451
470
|
exports.PLUGIN_MAIN_FILE = 'plugin.js';
|
|
452
471
|
const pluginWatchers = new Map();
|
|
453
472
|
async function rescan() {
|
|
@@ -607,7 +626,7 @@ function watchPlugin(id, path) {
|
|
|
607
626
|
async i18n(ctx) {
|
|
608
627
|
return (0, i18n_1.i18nFromTranslations)(await (0, lang_1.getLangData)(ctx), const_1.EMBEDDED_LANGUAGE);
|
|
609
628
|
},
|
|
610
|
-
});
|
|
629
|
+
}, unloadResources);
|
|
611
630
|
const folder = (0, path_1.dirname)(module);
|
|
612
631
|
const { sections, unwatch } = (0, customHtml_1.watchLoadCustomHtml)(folder);
|
|
613
632
|
pluginData.getCustomHtml = () => Object.assign(Object.fromEntries(sections), (0, misc_1.callable)(pluginData.customHtml) || {});
|
|
@@ -615,10 +634,7 @@ function watchPlugin(id, path) {
|
|
|
615
634
|
const plugin = new Plugin(id, folder, pluginData, async () => {
|
|
616
635
|
unwatchIcons();
|
|
617
636
|
unwatch();
|
|
618
|
-
|
|
619
|
-
x();
|
|
620
|
-
await Promise.allSettled(openDbs.map(x => x.close()));
|
|
621
|
-
openDbs.length = 0;
|
|
637
|
+
await unloadResources();
|
|
622
638
|
});
|
|
623
639
|
pluginReady.resolve();
|
|
624
640
|
if (alreadyRunning)
|
|
@@ -630,6 +646,12 @@ function watchPlugin(id, path) {
|
|
|
630
646
|
events_1.default.emit(wasInstalled ? 'pluginStarted' : 'pluginInstalled', plugin);
|
|
631
647
|
}
|
|
632
648
|
events_1.default.emit('pluginStarted:' + id);
|
|
649
|
+
async function unloadResources() {
|
|
650
|
+
for (const x of subbedConfigs)
|
|
651
|
+
x();
|
|
652
|
+
await Promise.allSettled(openDbs.map(x => x.close()));
|
|
653
|
+
openDbs.length = 0;
|
|
654
|
+
}
|
|
633
655
|
}
|
|
634
656
|
catch (e) {
|
|
635
657
|
await markItInactive();
|
package/src/selfCheck.js
CHANGED
|
@@ -24,7 +24,8 @@ async function selfCheck(url) {
|
|
|
24
24
|
const prjInfo = await (0, github_1.getProjectInfo)();
|
|
25
25
|
console.log(`Checking server ${url}`);
|
|
26
26
|
const parsed = new URL(url);
|
|
27
|
-
const
|
|
27
|
+
const hostname = (0, cross_1.normalizeHost)(parsed.hostname);
|
|
28
|
+
const family = !(0, net_1.isIP)(hostname) ? undefined : (0, net_1.isIPv6)(hostname) ? 6 : 4;
|
|
28
29
|
try {
|
|
29
30
|
++activeSelfChecks;
|
|
30
31
|
for (const services of lodash_1.default.chunk(lodash_1.default.shuffle(prjInfo.selfCheckServices), 2)) {
|
|
@@ -38,7 +39,7 @@ async function selfCheck(url) {
|
|
|
38
39
|
console.debug(svc);
|
|
39
40
|
body = applySymbols(body);
|
|
40
41
|
serviceUrl = applySymbols(serviceUrl);
|
|
41
|
-
const timeout =
|
|
42
|
+
const timeout = 9_000;
|
|
42
43
|
const res = await (0, cross_1.haveTimeout)(timeout, (0, util_http_1.httpString)(serviceUrl, { family, timeout, ...rest, body }));
|
|
43
44
|
const success = new RegExp(regexpSuccess).test(res);
|
|
44
45
|
const failure = new RegExp(regexpFailure).test(res);
|
|
@@ -60,7 +61,7 @@ async function selfCheck(url) {
|
|
|
60
61
|
--activeSelfChecks;
|
|
61
62
|
}
|
|
62
63
|
function applySymbols(s) {
|
|
63
|
-
return s?.replace('$IP',
|
|
64
|
+
return s?.replace('$IP', hostname)
|
|
64
65
|
.replace('$PORT', parsed.port || (parsed.protocol === 'https:' ? '443' : '80'))
|
|
65
66
|
.replace('$URL', url.replace(/\/$/, '') + CHECK_URL);
|
|
66
67
|
}
|
package/src/serveFile.js
CHANGED
|
@@ -25,7 +25,7 @@ const errorPages_1 = require("./errorPages");
|
|
|
25
25
|
const stream_1 = require("stream");
|
|
26
26
|
const crypto_1 = require("crypto");
|
|
27
27
|
const iconv_lite_1 = __importDefault(require("iconv-lite"));
|
|
28
|
-
const allowedReferer = (0, config_1.defineConfig)(
|
|
28
|
+
const allowedReferer = (0, config_1.defineConfig)(misc_1.CFG.allowed_referer, '');
|
|
29
29
|
const maxDownloads = downloadLimiter((0, config_1.defineConfig)(misc_1.CFG.max_downloads, 0), () => true);
|
|
30
30
|
const maxDownloadsPerIp = downloadLimiter((0, config_1.defineConfig)(misc_1.CFG.max_downloads_per_ip, 0), ctx => ctx.ip);
|
|
31
31
|
const maxDownloadsPerAccount = downloadLimiter((0, config_1.defineConfig)(misc_1.CFG.max_downloads_per_account, 0), ctx => (0, auth_1.getCurrentUsername)(ctx) || undefined);
|
|
@@ -66,13 +66,13 @@ async function serveFileNode(ctx, node) {
|
|
|
66
66
|
if (await maxDownloadsPerAccount(ctx) === undefined) // returning false will not execute other limits
|
|
67
67
|
await maxDownloads(ctx) || await maxDownloadsPerIp(ctx);
|
|
68
68
|
}
|
|
69
|
-
const mimeCfg = (0, config_1.defineConfig)(
|
|
69
|
+
const mimeCfg = (0, config_1.defineConfig)(misc_1.CFG.mime, {}, obj => {
|
|
70
70
|
const matchers = Object.keys(obj).map(k => (0, misc_1.makeMatcher)(k));
|
|
71
71
|
const values = Object.values(obj);
|
|
72
72
|
return (name) => values[matchers.findIndex(matcher => matcher(name))];
|
|
73
73
|
});
|
|
74
74
|
// after this number of seconds, the browser should check the server to see if there's a newer version of the file
|
|
75
|
-
const cacheControlDiskFiles = (0, config_1.defineConfig)(
|
|
75
|
+
const cacheControlDiskFiles = (0, config_1.defineConfig)(misc_1.CFG.cache_control_disk_files, 5);
|
|
76
76
|
async function serveFile(ctx, filePath, mime, cached) {
|
|
77
77
|
if (!filePath)
|
|
78
78
|
return;
|
|
@@ -11,7 +11,7 @@ const events_1 = __importDefault(require("./events"));
|
|
|
11
11
|
const cross_const_1 = require("./cross-const");
|
|
12
12
|
const upload_1 = require("./upload");
|
|
13
13
|
const multipartUpload_1 = require("./multipartUpload");
|
|
14
|
-
const
|
|
14
|
+
const promises_1 = require("stream/promises");
|
|
15
15
|
const stream_1 = require("stream");
|
|
16
16
|
const serveFile_1 = require("./serveFile");
|
|
17
17
|
const const_1 = require("./const");
|
|
@@ -24,11 +24,12 @@ const misc_1 = require("./misc");
|
|
|
24
24
|
const roots_1 = require("./roots");
|
|
25
25
|
const xxhashjs_1 = __importDefault(require("xxhashjs"));
|
|
26
26
|
const fs_1 = __importDefault(require("fs"));
|
|
27
|
-
const
|
|
27
|
+
const promises_2 = require("fs/promises");
|
|
28
28
|
const comments_1 = require("./comments");
|
|
29
29
|
const basicWeb_1 = require("./basicWeb");
|
|
30
30
|
const icons_1 = require("./icons");
|
|
31
31
|
const plugins_1 = require("./plugins");
|
|
32
|
+
const uploadOwners_1 = require("./uploadOwners");
|
|
32
33
|
const serveFrontendFiles = (0, serveGuiFiles_1.serveGuiFiles)(process.env.FRONTEND_PROXY, cross_const_1.FRONTEND_URI);
|
|
33
34
|
const serveFrontendPrefixed = (0, koa_mount_1.default)(cross_const_1.FRONTEND_URI.slice(0, -1), serveFrontendFiles);
|
|
34
35
|
const serveAdminFiles = (0, serveGuiFiles_1.serveGuiFiles)(process.env.ADMIN_PROXY, cross_const_1.ADMIN_URI);
|
|
@@ -70,7 +71,7 @@ const serveSharedFiles = async (ctx, next) => {
|
|
|
70
71
|
const decPath = (0, misc_1.safeDecodeURIComponent)(path, '');
|
|
71
72
|
const fn = (0, path_1.basename)(decPath);
|
|
72
73
|
const folderUri = (0, misc_1.pathEncode)((0, path_1.dirname)(decPath)); // re-encode to get readable urls
|
|
73
|
-
const folder = await (0, vfs_1.urlToNode)(folderUri, ctx, vfs_1.vfs, true); // we don't require the folder to already exist, but to be mapped on disk AND to have proper permissions
|
|
74
|
+
const folder = await (0, vfs_1.urlToNode)(folderUri, ctx, vfs_1.vfs.compiled(), true); // we don't require the folder to already exist, but to be mapped on disk AND to have proper permissions
|
|
74
75
|
if (!folder)
|
|
75
76
|
return (0, errorPages_1.sendErrorPage)(ctx, cross_const_1.HTTP_NOT_FOUND);
|
|
76
77
|
ctx.state.uploadPath = decPath;
|
|
@@ -110,8 +111,9 @@ const serveSharedFiles = async (ctx, next) => {
|
|
|
110
111
|
try {
|
|
111
112
|
if ((await events_1.default.emitAsync('deleting', { node, ctx }))?.isDefaultPrevented())
|
|
112
113
|
return ctx.status = cross_const_1.HTTP_FAILED_DEPENDENCY;
|
|
113
|
-
await (0,
|
|
114
|
+
await (0, promises_2.rm)(source, { recursive: true });
|
|
114
115
|
await (0, misc_1.deleteStoredFileAttrs)(source);
|
|
116
|
+
(0, uploadOwners_1.deleteUploadOwner)(path);
|
|
115
117
|
void (0, comments_1.setCommentFor)(source, ''); // necessary only to clean a possible descript.ion or kvstorage
|
|
116
118
|
return ctx.status = cross_const_1.HTTP_OK;
|
|
117
119
|
}
|
|
@@ -189,9 +191,8 @@ async function calcHash(fn, limit = Infinity) {
|
|
|
189
191
|
done();
|
|
190
192
|
}
|
|
191
193
|
});
|
|
192
|
-
fs_1.default.createReadStream(fn, { end: limit - 1 }).pipe(stream);
|
|
193
194
|
console.debug('Hashing', fn);
|
|
194
|
-
await (0,
|
|
195
|
+
await (0, promises_1.pipeline)(fs_1.default.createReadStream(fn, { end: limit - 1 }), stream);
|
|
195
196
|
console.debug('Hashed', fn);
|
|
196
197
|
return hash.digest().toString(16);
|
|
197
198
|
}
|
package/src/serveGuiFiles.js
CHANGED
|
@@ -104,6 +104,18 @@ async function treatIndex(ctx, filesUri, body) {
|
|
|
104
104
|
})));
|
|
105
105
|
const timestamp = await getFaviconTimestamp();
|
|
106
106
|
const lang = await (0, lang_1.getLangData)(ctx);
|
|
107
|
+
let group = 0; // this represents the loading-group: all plugins with the same group can be loaded concurrently
|
|
108
|
+
const loadScripts = (0, misc_1.onlyTruthy)((0, plugins_1.mapPlugins)((p, id) => {
|
|
109
|
+
const js = p.frontend_js?.map(f => f.includes('//') ? f : pub + id + '/' + f);
|
|
110
|
+
if (!js?.length)
|
|
111
|
+
return;
|
|
112
|
+
if (p.afterPlugin) // an afterPlugin constraint needs a loading barrier before this plugin's scripts
|
|
113
|
+
group++;
|
|
114
|
+
const ret = { id, group, js };
|
|
115
|
+
if (p.beforePlugin) // a beforePlugin constraint creates a loading barrier after this plugin's scripts
|
|
116
|
+
group++;
|
|
117
|
+
return ret;
|
|
118
|
+
}));
|
|
107
119
|
return body
|
|
108
120
|
.replace(/((?:src|href) *= *['"])\/?(?!([a-z]+:\/)?\/)(?!\?)/g, '$1' + ctx.state.revProxyPath + filesUri)
|
|
109
121
|
.replace(/<(\/)?(head|body)>/g, (all, isClose, name) => {
|
|
@@ -120,7 +132,7 @@ async function treatIndex(ctx, filesUri, body) {
|
|
|
120
132
|
pathSeparator: path_1.sep,
|
|
121
133
|
session: session instanceof apiMiddleware_1.ApiError ? null : session,
|
|
122
134
|
plugins,
|
|
123
|
-
loadScripts
|
|
135
|
+
loadScripts,
|
|
124
136
|
prefixUrl: ctx.state.revProxyPath || '',
|
|
125
137
|
proxyDetected: Boolean((0, middlewares_1.getProxyDetected)()),
|
|
126
138
|
dontOverwriteUploading: upload_1.dontOverwriteUploading.get(),
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.SERVER_CODE_SPLIT = void 0;
|
|
7
|
+
exports.parseServerCode = parseServerCode;
|
|
8
|
+
const lodash_1 = __importDefault(require("lodash"));
|
|
9
|
+
const cross_1 = require("./cross");
|
|
10
|
+
exports.SERVER_CODE_SPLIT = '//---';
|
|
11
|
+
function parseServerCode(text) {
|
|
12
|
+
const split = text.split(new RegExp('(?:^|\n)' + lodash_1.default.escapeRegExp(exports.SERVER_CODE_SPLIT), 'g'));
|
|
13
|
+
const ret = [];
|
|
14
|
+
let first = true;
|
|
15
|
+
for (const part of split)
|
|
16
|
+
if (first) { // first is special, no name
|
|
17
|
+
first = false;
|
|
18
|
+
const code = part.trim();
|
|
19
|
+
if (code)
|
|
20
|
+
ret.push({ name: '', code });
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
const [name, code] = (0, cross_1.splitAt)('\n', part);
|
|
24
|
+
ret.push({ name, code });
|
|
25
|
+
}
|
|
26
|
+
return ret;
|
|
27
|
+
}
|
package/src/throttler.js
CHANGED
|
@@ -15,11 +15,11 @@ const lodash_1 = __importDefault(require("lodash"));
|
|
|
15
15
|
const events_1 = __importDefault(require("./events"));
|
|
16
16
|
const persistence_1 = require("./persistence");
|
|
17
17
|
const mainThrottleGroup = new ThrottledStream_1.ThrottleGroup(Infinity);
|
|
18
|
-
(0, config_1.defineConfig)(
|
|
18
|
+
(0, config_1.defineConfig)(misc_1.CFG.max_kbps, Infinity).sub(v => mainThrottleGroup.updateLimit(v));
|
|
19
19
|
const ip2group = {};
|
|
20
20
|
const SymThrStr = Symbol('stream');
|
|
21
21
|
const SymTimeout = Symbol('timeout');
|
|
22
|
-
const maxKbpsPerIp = (0, config_1.defineConfig)(
|
|
22
|
+
const maxKbpsPerIp = (0, config_1.defineConfig)(misc_1.CFG.max_kbps_per_ip, Infinity);
|
|
23
23
|
maxKbpsPerIp.sub(v => {
|
|
24
24
|
for (const [ip, { group }] of Object.entries(ip2group))
|
|
25
25
|
if (ip) // empty-string = unlimited group
|
|
@@ -71,6 +71,7 @@ const throttler = async (ctx, next) => {
|
|
|
71
71
|
delete ip2group[ctx.ip];
|
|
72
72
|
});
|
|
73
73
|
ctx.state.originalStream = body;
|
|
74
|
+
body.on('error', e => ts.destroy(e)); // pipe doesn't forward source errors, so make Koa's response stream report them
|
|
74
75
|
ctx.body = body.pipe(ts);
|
|
75
76
|
if (downloadTotal !== undefined) // undefined will break SSE
|
|
76
77
|
ctx.response.length = downloadTotal; // preserve this info
|
package/src/update.js
CHANGED
|
@@ -28,8 +28,8 @@ const persistence_1 = require("./persistence");
|
|
|
28
28
|
const lodash_1 = __importDefault(require("lodash"));
|
|
29
29
|
const argv_1 = require("./argv");
|
|
30
30
|
const promises_2 = require("stream/promises");
|
|
31
|
-
const updateToBeta = (0, config_1.defineConfig)(
|
|
32
|
-
const autoCheckUpdate = (0, config_1.defineConfig)(
|
|
31
|
+
const updateToBeta = (0, config_1.defineConfig)(misc_1.CFG.update_to_beta, false);
|
|
32
|
+
const autoCheckUpdate = (0, config_1.defineConfig)(misc_1.CFG.auto_check_update, true);
|
|
33
33
|
const lastCheckUpdate = persistence_1.storedMap.singleSync('lastCheckUpdate', 0);
|
|
34
34
|
const AUTO_CHECK_EVERY = misc_1.DAY;
|
|
35
35
|
exports.autoCheckUpdateResult = persistence_1.storedMap.singleSync('autoCheckUpdateResult', undefined);
|
|
@@ -162,7 +162,13 @@ async function update(tagOrUrl = '') {
|
|
|
162
162
|
} while ((0, fs_1.existsSync)((0, path_1.join)(binPath, newBinFile)));
|
|
163
163
|
plugins_1.pluginsWatcher.pause();
|
|
164
164
|
try {
|
|
165
|
-
|
|
165
|
+
try {
|
|
166
|
+
await (0, misc_1.unzip)((0, fs_1.createReadStream)(LOCAL_UPDATE), path => (0, path_1.join)(binPath, path === binFile ? newBinFile : path));
|
|
167
|
+
}
|
|
168
|
+
catch (e) {
|
|
169
|
+
await (0, promises_1.rename)(LOCAL_UPDATE, 'hfs-update-bad.zip'); // quarantine failed archives so they don't take precedence over future downloads
|
|
170
|
+
throw e;
|
|
171
|
+
}
|
|
166
172
|
const newBin = (0, path_1.join)(binPath, newBinFile);
|
|
167
173
|
if (!(0, fs_1.existsSync)(newBin)) {
|
|
168
174
|
if (url) // the file was downloaded, and the UI would show the "update from local file" button until we remove it
|
package/src/upload.js
CHANGED
|
@@ -23,10 +23,15 @@ const events_1 = __importDefault(require("./events"));
|
|
|
23
23
|
const promises_1 = require("fs/promises");
|
|
24
24
|
const expiringCache_1 = require("./expiringCache");
|
|
25
25
|
const first_1 = require("./first");
|
|
26
|
-
|
|
27
|
-
exports.
|
|
28
|
-
exports.
|
|
26
|
+
const uploadOwners_1 = require("./uploadOwners");
|
|
27
|
+
exports.deleteUnfinishedUploadsAfter = (0, config_1.defineConfig)(misc_1.CFG.delete_unfinished_uploads_after, 86_400);
|
|
28
|
+
exports.minAvailableMb = (0, config_1.defineConfig)(misc_1.CFG.min_available_mb, 100);
|
|
29
|
+
exports.dontOverwriteUploading = (0, config_1.defineConfig)(misc_1.CFG.dont_overwrite_uploading, true);
|
|
29
30
|
const waitingToBeDeleted = {};
|
|
31
|
+
function cancelDeletion(path) {
|
|
32
|
+
clearTimeout(waitingToBeDeleted[path]?.timeout);
|
|
33
|
+
delete waitingToBeDeleted[path];
|
|
34
|
+
}
|
|
30
35
|
(0, first_1.onProcessExit)(() => {
|
|
31
36
|
if (!Object.keys(waitingToBeDeleted).length)
|
|
32
37
|
return;
|
|
@@ -48,7 +53,7 @@ function setUploadMeta(path, ctx) {
|
|
|
48
53
|
});
|
|
49
54
|
}
|
|
50
55
|
function getUploadTempFor(fullPath) {
|
|
51
|
-
return (0, path_1.join)((0, path_1.dirname)(fullPath),
|
|
56
|
+
return (0, path_1.join)((0, path_1.dirname)(fullPath), const_1.UPLOAD_TEMP_PREFIX + (0, path_1.basename)(fullPath).slice(-200));
|
|
52
57
|
}
|
|
53
58
|
const diskSpaceCache = (0, expiringCache_1.expiringCache)(3_000); // invalidate shortly
|
|
54
59
|
const uploadingFiles = new Map();
|
|
@@ -105,6 +110,7 @@ function uploadWriter(base, baseUri, filename, ctx) {
|
|
|
105
110
|
setUploadMeta(dir, ctx);
|
|
106
111
|
// use temporary name while uploading
|
|
107
112
|
const tempName = getUploadTempFor(fullPath);
|
|
113
|
+
const tempUri = (0, misc_1.enforceFinal)('/', baseUri) + (0, misc_1.pathEncode)((0, path_1.basename)(tempName));
|
|
108
114
|
// try to catch errors early (sync): this is avoiding on chrome139 when uploading a big file (1GB) to get miss the error code and have to make a `simulate`
|
|
109
115
|
try {
|
|
110
116
|
fs_1.default.accessSync(tempName, fs_1.default.constants.W_OK);
|
|
@@ -130,8 +136,11 @@ function uploadWriter(base, baseUri, filename, ctx) {
|
|
|
130
136
|
return fail(const_1.HTTP_PRECONDITION_FAILED);
|
|
131
137
|
}
|
|
132
138
|
// append if resuming
|
|
133
|
-
if (!resume && stats)
|
|
139
|
+
if (!resume && stats) {
|
|
140
|
+
// a new upload discards the old resumable temp, so its owner grant must not survive
|
|
141
|
+
(0, uploadOwners_1.deleteUploadOwner)(tempUri);
|
|
134
142
|
fs_1.default.unlinkSync(tempName);
|
|
143
|
+
}
|
|
135
144
|
const writeStream = (0, misc_1.createStreamLimiter)(isNaN(contentLength) ? Infinity : contentLength);
|
|
136
145
|
const fullSize = stillToWrite + resume;
|
|
137
146
|
// allow plugins to mess with the write-stream, because the read-stream can be complicated in case of multipart
|
|
@@ -152,6 +161,9 @@ function uploadWriter(base, baseUri, filename, ctx) {
|
|
|
152
161
|
const tracked = { ctx, got: 0, size: stillToWrite };
|
|
153
162
|
uploadingFiles.set(fullPath, tracked);
|
|
154
163
|
console.debug('Upload started');
|
|
164
|
+
let tempOwnerSet = false;
|
|
165
|
+
// ownership must be visible before a follow-up request can act on the unfinished file
|
|
166
|
+
ctx.req.once('aborted', setTempOwner);
|
|
155
167
|
// the file stream doesn't have an event for data being written, so we use 'data' of its feeder, which happens before, so we postpone a bit, trying to have a fresher number
|
|
156
168
|
writeStream.on('data', () => setTimeout(() => tracked.got = bytesGot()));
|
|
157
169
|
const lockMiddleware = (0, misc_1.pendingPromise)(); // expose outside, to let know when all operations stopped
|
|
@@ -171,7 +183,8 @@ function uploadWriter(base, baseUri, filename, ctx) {
|
|
|
171
183
|
return (0, promises_1.rm)(tempName).catch(() => { });
|
|
172
184
|
if (ctx.isAborted()) { // in the very unlikely case the connection is interrupted between last-byte and here, we still consider it unfinished, as the client had no way to know, and will resume, but it would get an error if we finish the process
|
|
173
185
|
const sec = exports.deleteUnfinishedUploadsAfter.get();
|
|
174
|
-
|
|
186
|
+
setTempOwner();
|
|
187
|
+
return lodash_1.default.isNumber(sec) && delayedDelete(tempName, sec, tempUri);
|
|
175
188
|
}
|
|
176
189
|
if (isPartial) // we are supposed to leave the unfinished upload as it is, with its temp name
|
|
177
190
|
return ctx.status = const_1.HTTP_NO_CONTENT; // lockMiddleware contains an empty string, so we must take care of the status
|
|
@@ -194,6 +207,7 @@ function uploadWriter(base, baseUri, filename, ctx) {
|
|
|
194
207
|
() => (0, promises_1.rename)(tempName, dest).then(() => true, e => e?.code !== 'EBUSY' && Promise.reject(e)), { timeout: 10_000 });
|
|
195
208
|
if (!done)
|
|
196
209
|
throw 'EBUSY';
|
|
210
|
+
(0, uploadOwners_1.deleteUploadOwner)(tempUri); // the temp URI no longer exists after rename; final ownership is recorded below
|
|
197
211
|
if (mtime) // so we use it to touch the file
|
|
198
212
|
await (0, promises_1.utimes)(dest, Date.now() / 1000, mtime / 1000);
|
|
199
213
|
cancelDeletion(tempName); // not necessary, as deletion's failure is silent, but still
|
|
@@ -202,6 +216,7 @@ function uploadWriter(base, baseUri, filename, ctx) {
|
|
|
202
216
|
if (ctx.query.comment)
|
|
203
217
|
void (0, comments_1.setCommentFor)(dest, String(ctx.query.comment));
|
|
204
218
|
obj.uri = (0, misc_1.enforceFinal)('/', baseUri) + (0, misc_1.pathEncode)((0, path_1.basename)(dest));
|
|
219
|
+
await (0, uploadOwners_1.setUploadOwner)(obj.uri, ctx);
|
|
205
220
|
events_1.default.emit('uploadFinished', obj);
|
|
206
221
|
console.debug("Upload finished", dest);
|
|
207
222
|
if (resEvent)
|
|
@@ -245,6 +260,12 @@ function uploadWriter(base, baseUri, filename, ctx) {
|
|
|
245
260
|
function bytesGot() {
|
|
246
261
|
return fileStream.bytesWritten + fileStream.writableLength;
|
|
247
262
|
}
|
|
263
|
+
function setTempOwner() {
|
|
264
|
+
if (tempOwnerSet)
|
|
265
|
+
return;
|
|
266
|
+
tempOwnerSet = true;
|
|
267
|
+
void (0, uploadOwners_1.setUploadOwner)(tempUri, ctx);
|
|
268
|
+
}
|
|
248
269
|
}
|
|
249
270
|
catch (e) {
|
|
250
271
|
releaseFile();
|
|
@@ -259,21 +280,18 @@ function uploadWriter(base, baseUri, filename, ctx) {
|
|
|
259
280
|
overwriteRequestedButForbidden = true;
|
|
260
281
|
return false;
|
|
261
282
|
}
|
|
262
|
-
function delayedDelete(path, secs) {
|
|
283
|
+
function delayedDelete(path, secs, tempUri) {
|
|
263
284
|
clearTimeout(waitingToBeDeleted[path]?.timeout);
|
|
264
285
|
return waitingToBeDeleted[path] = {
|
|
265
286
|
mtime,
|
|
266
287
|
expires: Date.now() + secs * 1000,
|
|
267
288
|
timeout: setTimeout(() => {
|
|
268
289
|
delete waitingToBeDeleted[path];
|
|
290
|
+
(0, uploadOwners_1.deleteUploadOwner)(tempUri);
|
|
269
291
|
(0, promises_1.rm)(path).catch(() => { });
|
|
270
292
|
}, secs * 1000)
|
|
271
293
|
};
|
|
272
294
|
}
|
|
273
|
-
function cancelDeletion(path) {
|
|
274
|
-
clearTimeout(waitingToBeDeleted[path]?.timeout);
|
|
275
|
-
delete waitingToBeDeleted[path];
|
|
276
|
-
}
|
|
277
295
|
function releaseFile() {
|
|
278
296
|
uploadingFiles.delete(fullPath);
|
|
279
297
|
}
|