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.
Files changed (238) hide show
  1. package/admin/assets/{af-BDoqNBMg.js → af-CevhRske.js} +1 -1
  2. package/admin/assets/{am-DXBvKJ3X.js → am-4ouVTwuo.js} +1 -1
  3. package/admin/assets/{ar-BJIoxKTh.js → ar-71cK8dbb.js} +1 -1
  4. package/admin/assets/{ar-dz-C9l3uc_3.js → ar-dz-CO7oh1gG.js} +1 -1
  5. package/admin/assets/{ar-iq-DKH8u2lX.js → ar-iq-CjG6db8J.js} +1 -1
  6. package/admin/assets/{ar-kw-D3gJalQp.js → ar-kw-DMsdR225.js} +1 -1
  7. package/admin/assets/{ar-ly-BZJzWFeR.js → ar-ly-ZhxIwUOA.js} +1 -1
  8. package/admin/assets/{ar-ma-DabiOCr0.js → ar-ma-Bo3Z1RyK.js} +1 -1
  9. package/admin/assets/{ar-sa-C7suzSCY.js → ar-sa-Db44biae.js} +1 -1
  10. package/admin/assets/{ar-tn-BvBucEx0.js → ar-tn-DNWRixE0.js} +1 -1
  11. package/admin/assets/{az-CPUppH2M.js → az-B6LcmVzB.js} +1 -1
  12. package/admin/assets/{be-vzvhX2dl.js → be-Dp0KhO5x.js} +1 -1
  13. package/admin/assets/{bg-09Xbj_Y3.js → bg-TsOJJvBm.js} +1 -1
  14. package/admin/assets/{bi-CvhiuiE9.js → bi-DstczOGo.js} +1 -1
  15. package/admin/assets/{bm-DtJAZAkZ.js → bm-Bf09NN15.js} +1 -1
  16. package/admin/assets/{bn-Dnzh2x6I.js → bn-Da3tHLgx.js} +1 -1
  17. package/admin/assets/{bn-bd-xEy4797g.js → bn-bd-CeSalisC.js} +1 -1
  18. package/admin/assets/{bo-vuPmptG3.js → bo-Ctxe0IKQ.js} +1 -1
  19. package/admin/assets/{br-C9xH4OD3.js → br-Y3dPq3Tj.js} +1 -1
  20. package/admin/assets/{bs-BB_uCUp1.js → bs-BbAlSeZx.js} +1 -1
  21. package/admin/assets/{ca-D7LKaf98.js → ca-8-Nn97gi.js} +1 -1
  22. package/admin/assets/{cs-DMFTNdNC.js → cs-BD5ArFNW.js} +1 -1
  23. package/admin/assets/{cv-DNUjJwnO.js → cv-CdMFVXGr.js} +1 -1
  24. package/admin/assets/{cy-qmAOTPwC.js → cy-B3QEYlIT.js} +1 -1
  25. package/admin/assets/{da-DDX54iM9.js → da-Bju2YQ9G.js} +1 -1
  26. package/admin/assets/{de-BBmlsfbM.js → de-DdzCTquR.js} +1 -1
  27. package/admin/assets/{de-at-CuMZqYfu.js → de-at-DgyKCkPl.js} +1 -1
  28. package/admin/assets/{de-ch-s8OXKb-5.js → de-ch-Dk9qkzvn.js} +1 -1
  29. package/admin/assets/{dist-CHP3cW_n.js → dist-KY_8edrw.js} +1 -1
  30. package/admin/assets/{dv-Cjs3QDBN.js → dv-C5KWK8o4.js} +1 -1
  31. package/admin/assets/{el-BsAOrUDP.js → el-BDL_lPlM.js} +1 -1
  32. package/admin/assets/{en-DQ460ET7.js → en-Ds3H1Wec.js} +1 -1
  33. package/admin/assets/{en-au-8tMHM2OT.js → en-au-CgltfNUb.js} +1 -1
  34. package/admin/assets/{en-ca-c9jM4M_8.js → en-ca-DEsKzbWO.js} +1 -1
  35. package/admin/assets/{en-in-D2ULzXMp.js → en-gb-21AYC3Ik.js} +1 -1
  36. package/admin/assets/{en-ie-D4_e3kV6.js → en-ie-aN3g-yqK.js} +1 -1
  37. package/admin/assets/{en-il-BoaZOven.js → en-il-CZl2d7NK.js} +1 -1
  38. package/admin/assets/{en-tt-Cn72OR4N.js → en-in-C7Kv4c3w.js} +1 -1
  39. package/admin/assets/{en-nz-grrmB3dg.js → en-nz-DA_sIaGR.js} +1 -1
  40. package/admin/assets/{en-sg-Cmqxpoir.js → en-sg-CP5PJpZf.js} +1 -1
  41. package/admin/assets/{en-gb-unFIXx9f.js → en-tt-B5vDLhL7.js} +1 -1
  42. package/admin/assets/{eo-Cqxmc0TT.js → eo-VE1KDD1u.js} +1 -1
  43. package/admin/assets/{es-CkjKdFZ3.js → es-SUzi5oTU.js} +1 -1
  44. package/admin/assets/{es-do-CTcTshup.js → es-do-Iri0eWm8.js} +1 -1
  45. package/admin/assets/{es-mx-DMXgMMxs.js → es-mx-DwneKeOc.js} +1 -1
  46. package/admin/assets/{es-pr-DNCV91L3.js → es-pr-C4zPUdA1.js} +1 -1
  47. package/admin/assets/{es-us-C7o2mlIH.js → es-us-BPS67etc.js} +1 -1
  48. package/admin/assets/{esm-DvUbRuYx.js → esm-CyNQ7bBW.js} +1 -1
  49. package/admin/assets/{et-DaPpH-gK.js → et-Ciqbfae0.js} +1 -1
  50. package/admin/assets/{eu-DfKyFruu.js → eu-CGhOwQpe.js} +1 -1
  51. package/admin/assets/{fa-xWrBjePb.js → fa-BWZiAv_E.js} +1 -1
  52. package/admin/assets/{fi-BOWDMsDS.js → fi-DJmgkVsJ.js} +1 -1
  53. package/admin/assets/{fo-B3FupW8R.js → fo-DVez5N-g.js} +1 -1
  54. package/admin/assets/{fr-DZBDBkwp.js → fr-D206itJo.js} +1 -1
  55. package/admin/assets/{fr-ca-CRePPJJ5.js → fr-ca-C6JiEHfi.js} +1 -1
  56. package/admin/assets/{fr-ch-pFKvYU8D.js → fr-ch-DUTEuSU_.js} +1 -1
  57. package/admin/assets/{fy-0k7RzHUH.js → fy-C_r06zH-.js} +1 -1
  58. package/admin/assets/{ga-D31rB2ys.js → ga-CrdQqAN5.js} +1 -1
  59. package/admin/assets/{gd-CbgY-FFE.js → gd-iWHfhKAH.js} +1 -1
  60. package/admin/assets/{gl-DrtvS9gt.js → gl-zgfy-pQ0.js} +1 -1
  61. package/admin/assets/{gom-latn-Dkke3GyE.js → gom-latn-CMZ7Vj7V.js} +1 -1
  62. package/admin/assets/{gu-BlEipYhR.js → gu-DNqIMjZc.js} +1 -1
  63. package/admin/assets/{he-BrHT6OLB.js → he-OJBbYFzL.js} +1 -1
  64. package/admin/assets/{hi-Bx0zlAgr.js → hi-ByrMhKpX.js} +1 -1
  65. package/admin/assets/{hr-CoFYGbr6.js → hr-CfRZXzCu.js} +1 -1
  66. package/admin/assets/{ht-Df5xKo4a.js → ht-DDrMtLzJ.js} +1 -1
  67. package/admin/assets/{hu-BFTN3q6-.js → hu-Ckeq3R6_.js} +1 -1
  68. package/admin/assets/{hy-am-DuZTJQed.js → hy-am-OpZy7Nvg.js} +1 -1
  69. package/admin/assets/{id-DBZ5nPkf.js → id-BE8-Ys79.js} +1 -1
  70. package/admin/assets/{index-BGIe1xaR.js → index-34fRsXFH.js} +128 -123
  71. package/admin/assets/{is-CmeHm47B.js → is-BOxJcH5N.js} +1 -1
  72. package/admin/assets/{it-Cv3fjOA9.js → it-BFlrIXvP.js} +1 -1
  73. package/admin/assets/{it-ch-DCTyHA3T.js → it-ch-ILAp-lZZ.js} +1 -1
  74. package/admin/assets/{ja-DHNVXAM5.js → ja-CbbYAYyn.js} +1 -1
  75. package/admin/assets/{jv-BdNIxhWv.js → jv-CuLkRQCH.js} +1 -1
  76. package/admin/assets/{ka-D-joD0QB.js → ka-BFeRDN-I.js} +1 -1
  77. package/admin/assets/{kk-8wRNNjxz.js → kk-CMvQxaPk.js} +1 -1
  78. package/admin/assets/{km-dUWOg1uK.js → km-BdQbDUNF.js} +1 -1
  79. package/admin/assets/{kn-CwQbzel0.js → kn-CE5AS6Nx.js} +1 -1
  80. package/admin/assets/{ko-D_gn9Sfw.js → ko-Ctl1mHOt.js} +1 -1
  81. package/admin/assets/{ku-Dh3MRML3.js → ku-Blbo7opr.js} +1 -1
  82. package/admin/assets/{ky-CWYuRvil.js → ky-iCha_cZX.js} +1 -1
  83. package/admin/assets/{lb-Cc2xsSIx.js → lb-BaP0S6h1.js} +1 -1
  84. package/admin/assets/{lo-CkwNFDBz.js → lo-B_Q4zUMa.js} +1 -1
  85. package/admin/assets/{lt-HWyFDLXu.js → lt-BOaP1KGX.js} +1 -1
  86. package/admin/assets/{lv-C7Zx3OLO.js → lv-S1XMSUX1.js} +1 -1
  87. package/admin/assets/{me-DH3AqnfF.js → me-ChOLVJHC.js} +1 -1
  88. package/admin/assets/{mi-DMmUry6m.js → mi-CnoIUOGl.js} +1 -1
  89. package/admin/assets/{mk-D6MNZrzX.js → mk-BoEaxVXE.js} +1 -1
  90. package/admin/assets/{ml-B_ur8Zqf.js → ml-BuvmVn6P.js} +1 -1
  91. package/admin/assets/{mn-DeL8Ry7x.js → mn-2bvGd1HI.js} +1 -1
  92. package/admin/assets/{mr-C01qRXFL.js → mr-BAoAWQmy.js} +1 -1
  93. package/admin/assets/{ms-DFUZJzOm.js → ms-C2eT9r-f.js} +1 -1
  94. package/admin/assets/{ms-my-CUYHkebs.js → ms-my-D2ZXdcAf.js} +1 -1
  95. package/admin/assets/{mt-CE8t5jSj.js → mt-BL5JZC4T.js} +1 -1
  96. package/admin/assets/{my-Df96A8jZ.js → my-CuJ4L0Ji.js} +1 -1
  97. package/admin/assets/{nb-D0837ewu.js → nb-CjE6cT80.js} +1 -1
  98. package/admin/assets/{ne-BdK5TR0R.js → ne-BL9I0cb1.js} +1 -1
  99. package/admin/assets/{nl-oYKmSma6.js → nl-Dt3mtMln.js} +1 -1
  100. package/admin/assets/{nl-be-BfVC0cur.js → nl-be-Am1mjZK9.js} +1 -1
  101. package/admin/assets/{nn-IEUPnyc8.js → nn-ChSewyYy.js} +1 -1
  102. package/admin/assets/{oc-lnc-g_wSIROr.js → oc-lnc-x6_ChCE9.js} +1 -1
  103. package/admin/assets/{pa-in-gWhcLmZE.js → pa-in-DVyN8o2P.js} +1 -1
  104. package/admin/assets/{pl-H3-LoxVn.js → pl-CqcuAUGV.js} +1 -1
  105. package/admin/assets/{pt-hiX8RqVw.js → pt-B2BPpD7K.js} +1 -1
  106. package/admin/assets/{pt-br-B8UcNR6_.js → pt-br-oBVRyUzP.js} +1 -1
  107. package/admin/assets/{rn-BKupViLr.js → rn-B3e3nkfp.js} +1 -1
  108. package/admin/assets/{ro-DmhgIvml.js → ro-C2tWFx0Q.js} +1 -1
  109. package/admin/assets/{ru-CcpO-b8g.js → ru-CCsEUnFL.js} +1 -1
  110. package/admin/assets/{rw-DWX_5ZK0.js → rw-CaCkWlGA.js} +1 -1
  111. package/admin/assets/{sd-Dat8YgLw.js → sd-90Zixcez.js} +1 -1
  112. package/admin/assets/{se-nTBYCwx3.js → se-DaZXpLsK.js} +1 -1
  113. package/admin/assets/{sha512-CZz40GwW.js → sha512-DlGRFb_l.js} +1 -1
  114. package/admin/assets/{si-DG88w5El.js → si-CSoBAkgi.js} +1 -1
  115. package/admin/assets/{sk-s5FxW36J.js → sk-pgQaqa2z.js} +1 -1
  116. package/admin/assets/{sl-B-HyGrpA.js → sl-Cr5kSdA7.js} +1 -1
  117. package/admin/assets/{sq-DPFtfEC8.js → sq-Pxi2MZOP.js} +1 -1
  118. package/admin/assets/{sr-bBPg1zz4.js → sr-Fdc9-lC-.js} +1 -1
  119. package/admin/assets/{sr-cyrl-cXkYHMcP.js → sr-cyrl-06jNWqpg.js} +1 -1
  120. package/admin/assets/{ss-7nSqXJN7.js → ss-BjEzSMit.js} +1 -1
  121. package/admin/assets/{sv-B8mG1QK4.js → sv-CqWqUpcA.js} +1 -1
  122. package/admin/assets/{sv-fi-CotGRXSH.js → sv-fi-CsO2xQHd.js} +1 -1
  123. package/admin/assets/{sw-fAxU-Uck.js → sw-xQUdgYhn.js} +1 -1
  124. package/admin/assets/{ta-Dl0Z1pan.js → ta-epbFrZ1M.js} +1 -1
  125. package/admin/assets/{te-DmcKasFF.js → te-DusTwU6q.js} +1 -1
  126. package/admin/assets/{tet-D6mmvYpQ.js → tet-ylc9v4gl.js} +1 -1
  127. package/admin/assets/{tg-39Hf2-oO.js → tg-2QfVTAJf.js} +1 -1
  128. package/admin/assets/{th-BSmTJSFN.js → th-QIm0Mh7I.js} +1 -1
  129. package/admin/assets/{tk-m85E5igC.js → tk-140KMZeW.js} +1 -1
  130. package/admin/assets/{tl-ph-BlYxVvNt.js → tl-ph-S7IZkbx3.js} +1 -1
  131. package/admin/assets/{tlh-D1POnIeE.js → tlh-C2fYb34-.js} +1 -1
  132. package/admin/assets/{tr-BurtfrLy.js → tr-Be-IM6i4.js} +1 -1
  133. package/admin/assets/{tzl-BvyD0lOf.js → tzl-DMy30zde.js} +1 -1
  134. package/admin/assets/{tzm-DZHS0dK6.js → tzm-Cu882cHO.js} +1 -1
  135. package/admin/assets/{tzm-latn-yNW7CNCH.js → tzm-latn-BYxpYHet.js} +1 -1
  136. package/admin/assets/{ug-cn-rxs2pf5Y.js → ug-cn-Cl8Jqk2E.js} +1 -1
  137. package/admin/assets/{uk-CgZM-NnA.js → uk-B-8XymtC.js} +1 -1
  138. package/admin/assets/{ur-YxIR8yYD.js → ur-ChGuAlvQ.js} +1 -1
  139. package/admin/assets/{uz-CE9QsQMG.js → uz-QbfaW7fT.js} +1 -1
  140. package/admin/assets/{uz-latn-DiVdSW6G.js → uz-latn-DIQivLCy.js} +1 -1
  141. package/admin/assets/{vi-DKARV6qf.js → vi-C2CmYlDu.js} +1 -1
  142. package/admin/assets/{x-pseudo-BuAdOrXQ.js → x-pseudo-BWgm1XlP.js} +1 -1
  143. package/admin/assets/{yo-BcB85DhC.js → yo-8bNbPP6u.js} +1 -1
  144. package/admin/assets/{zh-DLmxfxzl.js → zh-CeNSRZ4r.js} +1 -1
  145. package/admin/assets/{zh-cn-ILRADZJi.js → zh-cn-BlbF5C_X.js} +1 -1
  146. package/admin/assets/{zh-hk-fT7fQk9_.js → zh-hk-kg1c_VSK.js} +1 -1
  147. package/admin/assets/{zh-tw-NqRXGP6v.js → zh-tw-BCl4UsPA.js} +1 -1
  148. package/admin/index.html +1 -1
  149. package/frontend/assets/{dist-legacy-BPToXNXu.js → dist-legacy-DYE1EOcc.js} +1 -1
  150. package/frontend/assets/index-legacy-Br-Lpsst.js +1 -0
  151. package/frontend/assets/{lib-legacy-CBk-_G90.js → lib-legacy-C4Gw_0kX.js} +1 -1
  152. package/frontend/assets/{sha512-legacy-CWrR2rkQ.js → sha512-legacy-D3RbPSpl.js} +1 -1
  153. package/frontend/index.html +1 -1
  154. package/npm-shrinkwrap.json +2 -2
  155. package/package.json +1 -1
  156. package/src/AsapStream.js +18 -14
  157. package/src/SendList.js +42 -16
  158. package/src/ThrottledStream.js +3 -3
  159. package/src/acme.js +23 -10
  160. package/src/adminApis.js +7 -6
  161. package/src/api.accounts.js +11 -7
  162. package/src/api.auth.js +8 -5
  163. package/src/api.cert.js +1 -1
  164. package/src/api.get_file_list.js +4 -3
  165. package/src/api.lang.js +3 -2
  166. package/src/api.monitor.js +1 -1
  167. package/src/api.net.js +3 -1
  168. package/src/api.plugins.js +2 -0
  169. package/src/api.vfs.js +5 -4
  170. package/src/apiMiddleware.js +5 -1
  171. package/src/basicWeb.js +1 -1
  172. package/src/block.js +1 -1
  173. package/src/commands.js +6 -5
  174. package/src/comments.js +29 -13
  175. package/src/config.js +7 -4
  176. package/src/consoleLog.js +5 -1
  177. package/src/cross-const.js +27 -1
  178. package/src/cross.js +32 -33
  179. package/src/ddns.js +1 -4
  180. package/src/events.js +1 -4
  181. package/src/fileAttr.js +1 -1
  182. package/src/frontEndApis.js +7 -3
  183. package/src/geo.js +8 -2
  184. package/src/github.js +10 -9
  185. package/src/index.js +1 -1
  186. package/src/ips.js +24 -3
  187. package/src/langs/hfs-lang-ar.json +1 -0
  188. package/src/langs/hfs-lang-bg.json +1 -0
  189. package/src/langs/hfs-lang-de.json +1 -0
  190. package/src/langs/hfs-lang-el.json +1 -0
  191. package/src/langs/hfs-lang-es.json +1 -0
  192. package/src/langs/hfs-lang-fi.json +1 -0
  193. package/src/langs/hfs-lang-fr.json +1 -0
  194. package/src/langs/hfs-lang-hu.json +1 -0
  195. package/src/langs/hfs-lang-it.json +1 -0
  196. package/src/langs/hfs-lang-ja.json +1 -0
  197. package/src/langs/hfs-lang-ko.json +1 -0
  198. package/src/langs/hfs-lang-lt.json +1 -0
  199. package/src/langs/hfs-lang-ms.json +1 -0
  200. package/src/langs/hfs-lang-nl.json +1 -0
  201. package/src/langs/hfs-lang-pl.json +1 -0
  202. package/src/langs/hfs-lang-pt-br.json +1 -0
  203. package/src/langs/hfs-lang-ro.json +1 -0
  204. package/src/langs/hfs-lang-ru.json +1 -0
  205. package/src/langs/hfs-lang-sr-latn.json +1 -0
  206. package/src/langs/hfs-lang-sr.json +1 -0
  207. package/src/langs/hfs-lang-th.json +1 -0
  208. package/src/langs/hfs-lang-tr.json +1 -0
  209. package/src/langs/hfs-lang-uk.json +1 -0
  210. package/src/langs/hfs-lang-vi.json +1 -0
  211. package/src/langs/hfs-lang-zh-tw.json +1 -0
  212. package/src/langs/hfs-lang-zh.json +1 -0
  213. package/src/listen.js +17 -13
  214. package/src/log.js +11 -2
  215. package/src/middlewares.js +4 -4
  216. package/src/misc.js +3 -3
  217. package/src/multipartUpload.js +1 -2
  218. package/src/nat.js +1 -1
  219. package/src/outboundProxy.js +7 -0
  220. package/src/perm.js +5 -3
  221. package/src/plugins.js +58 -36
  222. package/src/selfCheck.js +4 -3
  223. package/src/serveFile.js +3 -3
  224. package/src/serveGuiAndSharedFiles.js +7 -6
  225. package/src/serveGuiFiles.js +13 -1
  226. package/src/serverCode.js +27 -0
  227. package/src/throttler.js +3 -2
  228. package/src/update.js +9 -3
  229. package/src/upload.js +29 -11
  230. package/src/uploadOwners.js +95 -0
  231. package/src/util-files.js +4 -2
  232. package/src/util-http.js +3 -1
  233. package/src/vfs.js +28 -24
  234. package/src/walkDir.js +24 -11
  235. package/src/watchLoad.js +13 -2
  236. package/src/webdav.js +4 -2
  237. package/src/zip.js +1 -1
  238. package/frontend/assets/index-legacy-CmusDwvl.js +0 -1
@@ -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 && u !== usernameWas && getAccount(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)('accounts', {});
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)(500);
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
- (0, misc_1.patchKey)(pl, 'unload', was => () => {
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 was === 'function')
231
- return was(...arguments);
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 === exports.SERVER_CODE_ID || (lastStatus === ctx.status && lastBody === ctx.body))
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 !== exports.SERVER_CODE_ID) // we already printed 'reloading'
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 = '.'; // a name that will surely be not found among plugin folders
393
- const serverCode = (0, config_1.defineConfig)('server_code', '', async (script, { k }) => {
394
- try {
395
- (await serverCode.compiled())?.unload();
396
- }
397
- catch { }
398
- const res = {};
399
- try {
400
- new Function('exports,require', script)(res, require); // parse
401
- await initPlugin(res);
402
- res.getCustomHtml = () => (0, misc_1.callable)(res.customHtml) || {};
403
- return new Plugin(exports.SERVER_CODE_ID, '', res, lodash_1.default.noop);
404
- }
405
- catch (e) {
406
- return console.error(k + ':', e.message || String(e));
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 === exports.SERVER_CODE_ID)
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 === exports.SERVER_CODE_ID)
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)('enable_plugins', ['antibrute']);
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)('plugins_config', {});
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
- for (const x of subbedConfigs)
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 family = !(0, net_1.isIP)(parsed.hostname) ? undefined : (0, net_1.isIPv6)(parsed.hostname) ? 6 : 4;
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 = 8_000;
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', parsed.hostname)
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)('allowed_referer', '');
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)('mime', {}, obj => {
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)('cache_control_disk_files', 5);
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 events_2 = require("events");
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 promises_1 = require("fs/promises");
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, promises_1.rm)(source, { recursive: true });
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, events_2.once)(stream, 'finish');
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
  }
@@ -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: Object.fromEntries((0, plugins_1.mapPlugins)((p, id) => [id, p.frontend_js?.map(f => f.includes('//') ? f : pub + id + '/' + f)])),
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)('max_kbps', Infinity).sub(v => mainThrottleGroup.updateLimit(v));
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)('max_kbps_per_ip', Infinity);
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)('update_to_beta', false);
32
- const autoCheckUpdate = (0, config_1.defineConfig)('auto_check_update', true);
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
- await (0, misc_1.unzip)((0, fs_1.createReadStream)(LOCAL_UPDATE), path => (0, path_1.join)(binPath, path === binFile ? newBinFile : path));
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
- exports.deleteUnfinishedUploadsAfter = (0, config_1.defineConfig)('delete_unfinished_uploads_after', 86_400);
27
- exports.minAvailableMb = (0, config_1.defineConfig)('min_available_mb', 100);
28
- exports.dontOverwriteUploading = (0, config_1.defineConfig)('dont_overwrite_uploading', true);
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), 'hfs$upload-' + (0, path_1.basename)(fullPath).slice(-200));
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
- return lodash_1.default.isNumber(sec) && delayedDelete(tempName, sec);
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
  }