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
@@ -11,25 +11,29 @@ const const_1 = require("./const");
11
11
  const auth_1 = require("./auth");
12
12
  const misc_1 = require("./misc");
13
13
  const api_vfs_1 = require("./api.vfs");
14
- function prepareAccount(ac) {
15
- return ac && {
14
+ function serializeAccount(ac) {
15
+ if (!ac)
16
+ return;
17
+ const hasLogin = (0, perm_1.accountHasLoginMethod)(ac);
18
+ return {
16
19
  ...lodash_1.default.omit(ac, ['password', 'hashed_password', 'srp']),
17
20
  username: ac.username, // omit won't copy it because it's a hidden prop
18
21
  hasPassword: (0, perm_1.accountHasPassword)(ac),
19
- isGroup: !(0, perm_1.accountHasLoginMethod)(ac),
22
+ isGroup: !hasLogin,
20
23
  adminActualAccess: (0, perm_1.accountCanLoginAdmin)(ac),
21
- canLogin: (0, perm_1.accountHasLoginMethod)(ac) ? (0, perm_1.accountCanLogin)(ac) : undefined,
24
+ canLogin: hasLogin ? (0, perm_1.accountCanLogin)(ac) : undefined,
22
25
  canChangePassword: (0, perm_1.accountCanChangePassword)(ac),
23
26
  invalidated: auth_1.invalidateSessionBefore.get(ac.username),
24
27
  directMembers: Object.values(perm_1.accounts.get()).filter(a => a.belongs?.includes(ac.username)).map(x => x.username),
25
28
  members: (0, misc_1.with_)(Object.values(perm_1.accounts.get()), accounts => {
26
29
  const ret = [];
30
+ // breadth-first traversal follows indirect memberships and excludes already collected accounts
27
31
  let news = [ac.username];
28
32
  while (news.length) {
29
33
  news = accounts.filter(a => !ret.includes(a.username) && a.belongs?.some(x => news.includes(x))).map(x => x.username);
30
34
  ret.push(...news);
31
35
  }
32
- return lodash_1.default.uniq(ret).sort();
36
+ return ret.sort();
33
37
  })
34
38
  };
35
39
  }
@@ -41,11 +45,11 @@ exports.default = {
41
45
  },
42
46
  get_account({ username }, ctx) {
43
47
  (0, misc_1.apiAssertTypes)({ string_undefined: { username } });
44
- return prepareAccount((0, perm_1.getAccount)(username || (0, auth_1.getCurrentUsername)(ctx)))
48
+ return serializeAccount((0, perm_1.getAccount)(username || (0, auth_1.getCurrentUsername)(ctx)))
45
49
  || new apiMiddleware_1.ApiError(const_1.HTTP_NOT_FOUND);
46
50
  },
47
51
  get_accounts() {
48
- return { list: (0, misc_1.onlyTruthy)(Object.values(perm_1.accounts.get()).map(prepareAccount)) };
52
+ return { list: (0, misc_1.onlyTruthy)(Object.values(perm_1.accounts.get()).map(serializeAccount)) };
49
53
  },
50
54
  get_admins() {
51
55
  return { list: lodash_1.default.filter(perm_1.accounts.get(), perm_1.accountCanLoginAdmin).map(ac => ac.username) };
package/src/api.auth.js CHANGED
@@ -14,14 +14,18 @@ const auth_1 = require("./auth");
14
14
  const config_1 = require("./config");
15
15
  const events_1 = __importDefault(require("./events"));
16
16
  const misc_1 = require("./misc");
17
+ const uploadOwners_1 = require("./uploadOwners");
17
18
  const node_crypto_1 = require("node:crypto");
18
19
  const ongoingLogins = {}; // store data that doesn't fit session object
19
- const keepSessionAlive = (0, config_1.defineConfig)('keep_session_alive', true);
20
+ const keepSessionAlive = (0, config_1.defineConfig)(misc_1.CFG.keep_session_alive, true);
20
21
  const fakeSrpSecret = (0, node_crypto_1.randomBytes)(32);
21
22
  const refresh_session = async ({}, ctx) => {
23
+ if (!ctx.session)
24
+ return new apiMiddleware_1.ApiError(const_1.HTTP_SERVER_ERROR);
25
+ (0, uploadOwners_1.getSessionId)(ctx); // anonymous upload ownership must be bound before any abortible upload request
22
26
  const username = (0, auth_1.getCurrentUsername)(ctx);
23
27
  const isAdmin = (0, adminApis_1.ctxAdminAccess)(ctx) || undefined;
24
- return !ctx.session ? new apiMiddleware_1.ApiError(const_1.HTTP_SERVER_ERROR) : {
28
+ return {
25
29
  username,
26
30
  expandedUsername: Array.from((0, perm_1.expandUsername)(username)),
27
31
  isAdmin,
@@ -75,7 +79,7 @@ exports.authApis = {
75
79
  // keep the public handshake identifier independent of predictable application PRNG state
76
80
  const sid = (0, node_crypto_1.randomUUID)();
77
81
  ongoingLogins[sid] = srpServer;
78
- setTimeout(() => delete ongoingLogins[sid], 60_000);
82
+ setTimeout(() => delete ongoingLogins[sid], 60_000); // client must complete api sequence (loginSrp2) within 1 minute or will be discarded to avoid memory leaks
79
83
  ctx.session.loggingIn = { username, sid }; // temporarily store until process is complete
80
84
  return rest;
81
85
  }
@@ -110,7 +114,7 @@ exports.authApis = {
110
114
  return new apiMiddleware_1.ApiError(const_1.HTTP_NOT_FOUND);
111
115
  try {
112
116
  const M2 = await step1.step2(BigInt(pubKey), BigInt(proof))
113
- .catch(() => { throw ''; });
117
+ .catch(() => { throw ''; }); // falsy value for later
114
118
  await (0, auth_1.setLoggedIn)(ctx, username);
115
119
  return {
116
120
  proof: String(M2),
@@ -128,7 +132,6 @@ exports.authApis = {
128
132
  delete ongoingLogins[sid];
129
133
  }
130
134
  },
131
- // this api is here for consistency, but frontend is actually using
132
135
  async logout({}, ctx) {
133
136
  if (!ctx.session)
134
137
  return new apiMiddleware_1.ApiError(const_1.HTTP_SERVER_ERROR);
package/src/api.cert.js CHANGED
@@ -17,7 +17,7 @@ exports.default = {
17
17
  cert.validity.notBefore = new Date();
18
18
  cert.validity.notAfter = new Date();
19
19
  cert.validity.notAfter.setFullYear(cert.validity.notBefore.getFullYear() + 1);
20
- const attrs = Object.entries(attributes || {}).map(x => ({ name: x[0], value: x[1] }));
20
+ const attrs = Object.entries(attributes ?? {}).map(x => ({ name: x[0], value: x[1] }));
21
21
  cert.setSubject(attrs);
22
22
  cert.setIssuer(attrs);
23
23
  cert.sign(keys.privateKey);
@@ -75,7 +75,8 @@ const get_file_list = async ({ uri = '/', offset, limit, c, onlyFolders, onlyFil
75
75
  for await (const sub of walker) {
76
76
  let name = (0, vfs_1.getNodeName)(sub);
77
77
  name = (0, path_1.basename)(name) || name; // on Windows, basename('C:') === ''
78
- if (filterName && !filterName(name) || fileMask && !(0, vfs_1.nodeIsFolder)(sub) && !fileMask(name)
78
+ if (filterName && !filterName(name)
79
+ || fileMask && !(0, vfs_1.nodeIsFolder)(sub) && !fileMask(name)
79
80
  || filterComment && !filterComment(await (0, comments_1.getCommentFor)(sub.source) || ''))
80
81
  continue;
81
82
  const entry = await nodeToDirEntry(ctx, sub);
@@ -83,7 +84,7 @@ const get_file_list = async ({ uri = '/', offset, limit, c, onlyFolders, onlyFil
83
84
  continue;
84
85
  const cbParams = { entry, ctx, listUri: uri, node: sub };
85
86
  try {
86
- const res = await Promise.all(onDirEntryHandlers.map(({ id, cb }) => Promise.resolve().then(() => cb(cbParams)).catch(error => { throw { id, error }; })));
87
+ const res = await Promise.all(onDirEntryHandlers.map(({ id, cb }) => (0, misc_1.callAsPromise)(() => cb(cbParams)).catch(error => { throw { id, error }; })));
87
88
  if (res.some(x => x === false))
88
89
  continue;
89
90
  }
@@ -146,7 +147,7 @@ const get_file_list = async ({ uri = '/', offset, limit, c, onlyFolders, onlyFil
146
147
  };
147
148
  }
148
149
  catch {
149
- return null;
150
+ return null; // ignore files with problems
150
151
  }
151
152
  function filesInsideCould(n = node) {
152
153
  return (0, vfs_1.masksCouldGivePermission)(n.masks, 'can_read')
package/src/api.lang.js CHANGED
@@ -48,8 +48,9 @@ const apis = {
48
48
  code = (0, lang_1.file2code)(code);
49
49
  validateCode(code);
50
50
  const fn = (0, lang_1.code2file)(code);
51
- const s = content = String(content);
52
- if (!(0, misc_1.tryJson)(s))
51
+ const s = String(content);
52
+ const o = (0, misc_1.tryJson)(s);
53
+ if (!o?.translate)
53
54
  return new apiMiddleware_1.ApiError(const_1.HTTP_NOT_ACCEPTABLE, "bad content for file " + fn);
54
55
  await (0, promises_1.writeFile)(fn, s, 'utf8');
55
56
  }
@@ -47,7 +47,7 @@ exports.default = {
47
47
  connectionUpdated(conn, change) {
48
48
  if (conn.socket.closed || lodash_1.default.isEmpty(change))
49
49
  return;
50
- if (change.ctx) {
50
+ if (change.ctx) { // serialize ctx once so all monitor subscribers reuse the resulting change
51
51
  Object.assign(change, fromCtx(change.ctx));
52
52
  change.ctx = undefined;
53
53
  }
package/src/api.net.js CHANGED
@@ -61,7 +61,7 @@ exports.default = {
61
61
  if (external)
62
62
  await (0, nat_1.getUpnpClient)().createMapping((0, nat_1.upnpMappingParam)(internal || internalPort, external))
63
63
  .catch(res => {
64
- throw new apiMiddleware_1.ApiError(res.errorCode || const_1.HTTP_SERVER_ERROR, res.errorCode === 718 ? "Port not available" : res.errorDescription || res.message || "unknown error");
64
+ throw new apiMiddleware_1.ApiError(const_1.HTTP_SERVER_ERROR, res.errorCode === 718 ? "Port not available" : res.errorDescription || res.message || "unknown error");
65
65
  });
66
66
  nat_1.mappedPort.set(external || 0); // remember only successful HFS mappings
67
67
  return {};
@@ -84,6 +84,8 @@ exports.default = {
84
84
  },
85
85
  async make_cert({ domain, email, altNames }) {
86
86
  (0, misc_1.apiAssertTypes)({ string: { domain }, string_undefined: { email }, array_undefined: { altNames } });
87
+ if (!domain)
88
+ return new apiMiddleware_1.ApiError(const_1.HTTP_BAD_REQUEST, 'bad params');
87
89
  if (altNames?.some((name) => typeof name !== 'string'))
88
90
  return new apiMiddleware_1.ApiError(const_1.HTTP_BAD_REQUEST, 'bad altNames');
89
91
  await (0, acme_1.makeCert)(domain, email, altNames).catch(e => {
@@ -169,6 +169,8 @@ const apis = {
169
169
  },
170
170
  async uninstall_plugin({ id, deleteConfig }) {
171
171
  assertPluginId(id);
172
+ if (!(0, plugins_1.getPluginInfo)(id))
173
+ return new apiMiddleware_1.ApiError(const_1.HTTP_NOT_FOUND);
172
174
  await (0, plugins_1.stopPlugin)(id);
173
175
  await (0, promises_1.rm)(plugins_1.PATH + '/' + id, { recursive: true, force: true });
174
176
  if (deleteConfig)
package/src/api.vfs.js CHANGED
@@ -26,7 +26,7 @@ async function urlToNodeOriginal(uri) {
26
26
  exports.default = {
27
27
  async get_vfs() {
28
28
  return { root: await recur() };
29
- async function recur(node = vfs_1.vfs) {
29
+ async function recur(node = vfs_1.vfs.compiled()) {
30
30
  const { source } = node;
31
31
  const stats = await (0, vfs_1.nodeStats)(node);
32
32
  const isFolder = (0, vfs_1.nodeIsFolder)(node);
@@ -61,7 +61,7 @@ exports.default = {
61
61
  return new apiMiddleware_1.ApiError(const_1.HTTP_BAD_REQUEST, 'bad name');
62
62
  // check for siblings with the same name
63
63
  const parent = await urlToNodeOriginal((0, path_1.dirname)(uri));
64
- if (parent?.children?.find(x => (0, vfs_1.getNodeName)(x) === props.name))
64
+ if (parent?.children?.find(x => x !== n && (0, vfs_1.isSameFilenameAs)(props.name)(x)))
65
65
  return new apiMiddleware_1.ApiError(const_1.HTTP_CONFLICT, 'name already present');
66
66
  }
67
67
  Object.assign(n, sanitizeVfsProps(props));
@@ -112,7 +112,7 @@ exports.default = {
112
112
  return new apiMiddleware_1.ApiError(const_1.HTTP_BAD_REQUEST, 'name or source required');
113
113
  if (name && !(0, misc_1.isValidFileName)(name))
114
114
  return new apiMiddleware_1.ApiError(const_1.HTTP_BAD_REQUEST, 'bad name');
115
- const parentNode = parent ? await urlToNodeOriginal(parent) : vfs_1.vfs;
115
+ const parentNode = parent ? await urlToNodeOriginal(parent) : vfs_1.vfs.compiled();
116
116
  if (!parentNode)
117
117
  return new apiMiddleware_1.ApiError(const_1.HTTP_NOT_FOUND, 'parent not found');
118
118
  if (!(0, vfs_1.nodeIsFolder)(parentNode))
@@ -190,9 +190,10 @@ exports.default = {
190
190
  list.add({ n, k: 'd' });
191
191
  }
192
192
  catch (error) {
193
+ list.error(String(error));
193
194
  console.debug(error);
194
195
  }
195
- return;
196
+ return list.close();
196
197
  }
197
198
  const sendPropsAsap = (0, util_os_1.getDiskSpace)(path).then(x => x && list.props(x));
198
199
  try {
@@ -12,6 +12,7 @@ const misc_1 = require("./misc");
12
12
  const const_1 = require("./const");
13
13
  const config_1 = require("./config");
14
14
  const plugins_1 = require("./plugins");
15
+ const SendList_1 = require("./SendList");
15
16
  class ApiError extends Error {
16
17
  status;
17
18
  constructor(status, message) {
@@ -55,6 +56,9 @@ function apiMiddleware(apis) {
55
56
  }
56
57
  if (isAsyncGenerator(res))
57
58
  res = (0, misc_1.asyncGeneratorToReadable)(res);
59
+ // return the initial list as regular JSON when the caller did not request live updates
60
+ if (res instanceof SendList_1.SendListReadable && ctx.get('accept') !== 'text/event-stream')
61
+ res = await res.collectInitial();
58
62
  if (res instanceof stream_1.Readable) { // Readable, we'll go SSE-mode
59
63
  res.pipe((0, sse_1.default)(ctx));
60
64
  const resAsReadable = res; // satisfy ts
@@ -64,7 +68,7 @@ function apiMiddleware(apis) {
64
68
  }
65
69
  if (res instanceof ApiError)
66
70
  return send(res.status, res.message);
67
- if (res instanceof Error) // generic error/exception
71
+ if (res instanceof Error) // generic error/exception. expose unexpected errors to help diagnose bugs reported by users
68
72
  return send(const_1.HTTP_BAD_REQUEST, res.stack || res.message || String(res));
69
73
  ctx.body = res;
70
74
  function send(status, body) {
package/src/basicWeb.js CHANGED
@@ -13,7 +13,7 @@ const misc_1 = require("./misc");
13
13
  const lodash_1 = __importDefault(require("lodash"));
14
14
  const adminApis_1 = require("./adminApis");
15
15
  const customHtml_1 = require("./customHtml");
16
- const autoBasic = (0, config_1.defineConfig)('auto_basic', true, v => lodash_1.default.isString(v) ? new RegExp(v, 'i') : null);
16
+ const autoBasic = (0, config_1.defineConfig)(misc_1.CFG.auto_basic, true, v => lodash_1.default.isString(v) ? new RegExp(v, 'i') : null);
17
17
  function basicWeb(ctx, node) {
18
18
  const { get } = ctx.query;
19
19
  if (get === 'login') {
package/src/block.js CHANGED
@@ -13,7 +13,7 @@ const connections_1 = require("./connections");
13
13
  const misc_1 = require("./misc");
14
14
  const net_1 = require("net");
15
15
  const lodash_1 = __importDefault(require("lodash"));
16
- exports.block = (0, config_1.defineConfig)('block', [], rules => {
16
+ exports.block = (0, config_1.defineConfig)(misc_1.CFG.block, [], rules => {
17
17
  const now = new Date();
18
18
  const ret = !Array.isArray(rules) ? []
19
19
  : (0, misc_1.onlyTruthy)(rules.map(rule => {
package/src/commands.js CHANGED
@@ -54,7 +54,8 @@ const api_monitor_1 = __importStar(require("./api.monitor"));
54
54
  const connections_1 = require("./connections");
55
55
  const argv_1 = require("./argv");
56
56
  const listen_2 = require("./listen");
57
- let debugEnabled = argv_1.argv.debug || process.env.HFS_DEBUG || const_1.DEV;
57
+ const debug = (0, config_1.defineConfig)(cross_1.CFG.debug, Boolean(argv_1.argv.debug || process.env.HFS_DEBUG || const_1.DEV));
58
+ debug.dontStore();
58
59
  if (!argv_1.argv.updating && !config_1.showHelp) {
59
60
  try {
60
61
  // Not sure if the try is necessary for when stdin is unavailable, but someone reported a problem using nohup https://github.com/rejetto/hfs/issues/74 and I've found this example try-catching https://github.com/DefinitelyTyped/DefinitelyTyped/blob/dda83a906914489e09ca28afea12948529015d4a/types/node/readline.d.ts#L489
@@ -80,7 +81,7 @@ if (!argv_1.argv.updating && !config_1.showHelp) {
80
81
  for (const k of ['log', 'warn', 'error', 'debug']) {
81
82
  const original = console[k];
82
83
  console[k] = (...args) => {
83
- if (k === 'debug' && !debugEnabled)
84
+ if (k === 'debug' && !debug.get())
84
85
  return;
85
86
  if (!first_1.quitting && tty)
86
87
  clean();
@@ -96,7 +97,7 @@ if (!argv_1.argv.updating && !config_1.showHelp) {
96
97
  catch {
97
98
  console.log("Console commands not available");
98
99
  const original = console.debug;
99
- console.debug = (...args) => debugEnabled && original(...args);
100
+ console.debug = (...args) => debug.get() && original(...args);
100
101
  }
101
102
  }
102
103
  async function parseCommandLine(line) {
@@ -198,8 +199,8 @@ const commands = {
198
199
  debug: {
199
200
  params: '',
200
201
  cb() {
201
- debugEnabled = !debugEnabled;
202
- console.log(`Debug messages ${debugEnabled ? "on" : "off"}`);
202
+ debug.set(v => !v);
203
+ console.log(`Debug messages ${debug.get() ? "on" : "off"}`);
203
204
  }
204
205
  },
205
206
  'start-plugin': {
package/src/comments.js CHANGED
@@ -16,15 +16,16 @@ 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 promises_2 = require("stream/promises");
19
20
  const expiringCache_1 = require("./expiringCache");
20
21
  exports.DESCRIPT_ION = 'descript.ion';
21
22
  exports.DESCRIPT_ION_ALT = 'DESCRIPT.ION';
22
23
  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
23
- (0, config_1.defineConfig)('descript_ion', true, (v, more) => {
24
+ (0, config_1.defineConfig)(cross_1.CFG.descript_ion, true, (v, more) => {
24
25
  if (!v && more.version?.olderThan('0.57.0-alpha1'))
25
26
  commentsStorage.set('attr');
26
27
  });
27
- const descriptIonEncoding = (0, config_1.defineConfig)('descript_ion_encoding', 'utf8');
28
+ const descriptIonEncoding = (0, config_1.defineConfig)(cross_1.CFG.descript_ion_encoding, 'utf8');
28
29
  function readFromDescriptIon(path) {
29
30
  return usingDescriptIon() && readDescriptIon((0, path_1.dirname)(path)).then(x => x.get((0, path_1.basename)(path)), () => undefined);
30
31
  }
@@ -39,16 +40,29 @@ async function getCommentFor(path) {
39
40
  ]).then(([fromAttr, fromIon]) => fromAttr || fromIon || undefined);
40
41
  }
41
42
  async function setCommentFor(path, comment) {
42
- if (commentsStorage.get()) {
43
- await (0, misc_1.storeFileAttr)(path, COMMENT_ATTR, comment || undefined);
44
- return setCommentDescriptIon(path, '');
43
+ if (commentsStorage.get())
44
+ return prep((0, misc_1.storeFileAttr)(path, COMMENT_ATTR, comment || undefined)).then(v => {
45
+ if (v)
46
+ void setCommentDescriptIon(path, '').catch(() => { });
47
+ return v;
48
+ });
49
+ // should we also remove from file-attr, similarly as above we remove from descript.ion? not sure, but for the time we won't because #1 storeFileAttr is not really deleting, and we would store a lot of empty attributes, #2 more people will switch from descript.ion to attr (because introduced later) than the opposite
50
+ return prep(setCommentDescriptIon(path, comment));
51
+ function prep(p) {
52
+ return p.then(v => v !== false, (e) => {
53
+ console.error(`${comment ? "Error setting comment" : "Error removing comment"}: ${String(e)}`);
54
+ return false;
55
+ });
45
56
  }
46
- return setCommentDescriptIon(path, comment); // should we also remove from file-attr? not sure, but for the time we won't because #1 storeFileAttr is not really deleting, and we would store a lot of empty attributes, #2 more people will switch from descript.ion to attr (because introduced later) than the opposite
47
57
  }
48
- const setCommentDescriptIon = (0, misc_1.singleWorkerFromBatchWorker)(async (jobs) => {
49
- const byFolder = lodash_1.default.groupBy(jobs, job => (0, path_1.dirname)(job[0]));
50
- return Promise.allSettled(lodash_1.default.map(byFolder, async (jobs, folder) => {
51
- const comments = await readDescriptIon(folder).catch(() => new Map());
58
+ const setCommentDescriptIon = (0, misc_1.singleWorkerFromBatchWorker)((jobs) => {
59
+ const byFolder = lodash_1.default.groupBy(jobs, job => (0, path_1.dirname)(job[0])); // jobs in the same folder share one write and its success or failure
60
+ const resultByFolder = lodash_1.default.mapValues(byFolder, async (jobs, folder) => {
61
+ const comments = await readDescriptIon(folder).catch(e => {
62
+ if (e?.code !== 'ENOENT')
63
+ throw e;
64
+ return new Map();
65
+ });
52
66
  for (const [path, comment] of jobs) {
53
67
  const file = path.slice(folder.length + 1);
54
68
  if (!comment)
@@ -58,7 +72,7 @@ const setCommentDescriptIon = (0, misc_1.singleWorkerFromBatchWorker)(async (job
58
72
  }
59
73
  const path = await filePathHelper(folder);
60
74
  if (!comments.size)
61
- return (0, promises_1.unlink)(path);
75
+ return (0, promises_1.rm)(path, { force: true });
62
76
  // encode comments in descript.ion format
63
77
  const ws = await (0, util_files_1.createSafeWriteStream)(path);
64
78
  comments.forEach((comment, filename) => {
@@ -70,8 +84,10 @@ const setCommentDescriptIon = (0, misc_1.singleWorkerFromBatchWorker)(async (job
70
84
  ws.write(MULTILINE_SUFFIX, 'binary');
71
85
  ws.write('\n');
72
86
  });
73
- await new Promise(res => ws.end(res));
74
- }));
87
+ ws.end();
88
+ await (0, promises_2.finished)(ws);
89
+ });
90
+ return jobs.map(([path]) => resultByFolder[(0, path_1.dirname)(path)]);
75
91
  });
76
92
  function areCommentsEnabled() {
77
93
  return true; // true since we introduced comments in file-attr
package/src/config.js CHANGED
@@ -12,6 +12,7 @@ exports.getWholeConfig = getWholeConfig;
12
12
  exports.setConfig = setConfig;
13
13
  exports.subMultipleConfigs = subMultipleConfigs;
14
14
  const const_1 = require("./const");
15
+ const cross_const_1 = require("./cross-const");
15
16
  const watchLoad_1 = require("./watchLoad");
16
17
  const yaml_1 = __importDefault(require("yaml"));
17
18
  const lodash_1 = __importDefault(require("lodash"));
@@ -25,6 +26,7 @@ const argv_1 = require("./argv");
25
26
  const util_files_1 = require("./util-files");
26
27
  // keep definition of config properties
27
28
  const configProps = {};
29
+ const dontStore = [];
28
30
  let started = false; // this will tell the difference for subscribeConfig()s that are called before or after config is loaded
29
31
  let state = {}; // current state of config properties
30
32
  const filePath = (0, cross_1.with_)(argv_1.argv.config || process.env.HFS_CONFIG, p => {
@@ -62,7 +64,7 @@ class Version extends String {
62
64
  exports.Version = Version;
63
65
  const CONFIG_CHANGE_EVENT_PREFIX = 'config.';
64
66
  exports.currentVersion = new Version(const_1.VERSION);
65
- const configVersion = defineConfig('version', const_1.VERSION, v => new Version(v));
67
+ const configVersion = defineConfig(cross_const_1.CFG.version, const_1.VERSION, v => new Version(v));
66
68
  function defineConfig(k, defaultValue, compiler) {
67
69
  configProps[k] = { defaultValue };
68
70
  const object = {
@@ -104,7 +106,8 @@ function defineConfig(k, defaultValue, compiler) {
104
106
  compiled = v;
105
107
  const was = getConfig(k);
106
108
  return events_1.default.emitAsync(CONFIG_CHANGE_EVENT_PREFIX + k, was, was, const_1.VERSION, true);
107
- }
109
+ },
110
+ dontStore() { dontStore.push(k); },
108
111
  };
109
112
  let compiled = compiler?.(defaultValue, { k, version: exports.currentVersion, defaultValue, object });
110
113
  if (compiler)
@@ -185,11 +188,11 @@ const saveDebounced = (0, debounceAsync_1.debounceAsync)(async () => {
185
188
  const aWeekAgo = Date.now() - cross_1.DAY * 7;
186
189
  if (await (0, util_files_1.statWithTimeout)(bak).then(x => aWeekAgo > x.mtimeMs, () => true))
187
190
  await (0, promises_1.copyFile)(filePath, bak).catch(() => { }); // ignore errors
188
- await exports.configFile.save(stringify({
191
+ await exports.configFile.save(stringify(lodash_1.default.omit({
189
192
  ...state,
190
193
  version: const_1.VERSION,
191
194
  platform: `${process.platform}-${process.arch}${(0, cross_1.prefix)('-', !const_1.IS_BINARY && (0, path_1.basename)(process.execPath))}`,
192
- })).catch(err => console.error('Failed at saving config file, please ensure it is writable.', String(err)));
195
+ }, dontStore))).catch(err => console.error('Failed at saving config file, please ensure it is writable.', String(err)));
193
196
  });
194
197
  const saveConfigAsap = () => void saveDebounced();
195
198
  exports.saveConfigAsap = saveConfigAsap;
package/src/consoleLog.js CHANGED
@@ -11,7 +11,11 @@ const fs_1 = require("fs");
11
11
  const argv_1 = require("./argv");
12
12
  exports.consoleLog = [];
13
13
  const originalConsoleLog = console.log;
14
- const f = argv_1.argv.consoleFile ? (0, fs_1.createWriteStream)(argv_1.argv.consoleFile, { flags: 'a', encoding: 'utf8' }) : null;
14
+ let f = argv_1.argv.consoleFile ? (0, fs_1.createWriteStream)(argv_1.argv.consoleFile, { flags: 'a', encoding: 'utf8' }) : null;
15
+ f?.on('error', err => {
16
+ f = null; // stop using a failed stream so later logs cannot repeat the same error
17
+ console.error("Cannot write console file", argv_1.argv.consoleFile, String(err));
18
+ });
15
19
  let terminalOutputBroken = false;
16
20
  for (const stream of [process.stdout, process.stderr])
17
21
  stream.on('error', err => {
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.HTTP_MESSAGES = exports.HTTP_INSUFFICIENT_STORAGE = exports.HTTP_SERVICE_UNAVAILABLE = exports.HTTP_SERVER_ERROR = exports.HTTP_TOO_MANY_REQUESTS = exports.HTTP_FAILED_DEPENDENCY = exports.HTTP_LOCKED = exports.HTTP_FOOL = exports.HTTP_RANGE_NOT_SATISFIABLE = exports.HTTP_PAYLOAD_TOO_LARGE = exports.HTTP_PRECONDITION_FAILED = exports.HTTP_LENGTH_REQUIRED = exports.HTTP_CONFLICT = exports.HTTP_NOT_ACCEPTABLE = exports.HTTP_METHOD_NOT_ALLOWED = exports.HTTP_NOT_FOUND = exports.HTTP_FORBIDDEN = exports.HTTP_UNAUTHORIZED = exports.HTTP_BAD_REQUEST = exports.HTTP_NOT_MODIFIED = exports.HTTP_TEMPORARY_REDIRECT = exports.HTTP_MOVED_PERMANENTLY = exports.HTTP_PARTIAL_CONTENT = exports.HTTP_NO_CONTENT = exports.HTTP_CREATED = exports.HTTP_OK = exports.BASIC_AUTHENTICATE_HEADER = exports.EMBEDDED_LANGUAGE = exports.MASK_IN_TESTS = exports.HIDE_IN_TESTS = exports.ALLOW_SESSION_IP_CHANGE = exports.PREVIOUS_TAG = exports.MTIME_CHECK = exports.UPLOAD_TEMP_HASH = exports.HFS_REPO = exports.PLUGIN_CUSTOM_REST_PREFIX = exports.NBSP = exports.PORT_DISABLED = exports.ICONS_URI = exports.PLUGINS_PUB_URI = exports.API_URI = exports.ADMIN_URI = exports.FRONTEND_URI = exports.SPECIAL_URI = void 0;
3
+ exports.HTTP_MESSAGES = exports.HTTP_INSUFFICIENT_STORAGE = exports.HTTP_SERVICE_UNAVAILABLE = exports.HTTP_SERVER_ERROR = exports.HTTP_TOO_MANY_REQUESTS = exports.HTTP_FAILED_DEPENDENCY = exports.HTTP_LOCKED = exports.HTTP_FOOL = exports.HTTP_RANGE_NOT_SATISFIABLE = exports.HTTP_PAYLOAD_TOO_LARGE = exports.HTTP_PRECONDITION_FAILED = exports.HTTP_LENGTH_REQUIRED = exports.HTTP_CONFLICT = exports.HTTP_NOT_ACCEPTABLE = exports.HTTP_METHOD_NOT_ALLOWED = exports.HTTP_NOT_FOUND = exports.HTTP_FORBIDDEN = exports.HTTP_UNAUTHORIZED = exports.HTTP_BAD_REQUEST = exports.HTTP_NOT_MODIFIED = exports.HTTP_TEMPORARY_REDIRECT = exports.HTTP_MOVED_PERMANENTLY = exports.HTTP_PARTIAL_CONTENT = exports.HTTP_NO_CONTENT = exports.HTTP_CREATED = exports.HTTP_OK = exports.CFG = exports.BASIC_AUTHENTICATE_HEADER = exports.EMBEDDED_LANGUAGE = exports.MASK_IN_TESTS = exports.HIDE_IN_TESTS = exports.ALLOW_SESSION_IP_CHANGE = exports.PREVIOUS_TAG = exports.MTIME_CHECK = exports.UPLOAD_TEMP_HASH = exports.UPLOAD_TEMP_PREFIX = exports.HFS_REPO = exports.PLUGIN_CUSTOM_REST_PREFIX = exports.NBSP = exports.PORT_DISABLED = exports.ICONS_URI = exports.PLUGINS_PUB_URI = exports.API_URI = exports.ADMIN_URI = exports.FRONTEND_URI = exports.SPECIAL_URI = void 0;
4
4
  exports.SPECIAL_URI = '/~/';
5
5
  exports.FRONTEND_URI = exports.SPECIAL_URI + 'frontend/';
6
6
  exports.ADMIN_URI = exports.SPECIAL_URI + 'admin/';
@@ -11,6 +11,7 @@ exports.PORT_DISABLED = -1;
11
11
  exports.NBSP = '\xA0';
12
12
  exports.PLUGIN_CUSTOM_REST_PREFIX = '_';
13
13
  exports.HFS_REPO = 'rejetto/hfs';
14
+ exports.UPLOAD_TEMP_PREFIX = 'hfs$upload-';
14
15
  exports.UPLOAD_TEMP_HASH = 'upload-temp-hash';
15
16
  exports.MTIME_CHECK = 'x-mtime-check';
16
17
  exports.PREVIOUS_TAG = 'previous';
@@ -19,6 +20,31 @@ exports.HIDE_IN_TESTS = 'hideInTests'; // elements that have variable size, wher
19
20
  exports.MASK_IN_TESTS = 'maskInTests';
20
21
  exports.EMBEDDED_LANGUAGE = 'en'; // frontend includes this language in the code, and not need to import the translation-json
21
22
  exports.BASIC_AUTHENTICATE_HEADER = 'Basic realm="HFS"';
23
+ exports.CFG = constMap([
24
+ 'accounts', 'acme_domain', 'acme_renew', 'admin_net', 'allowed_referer',
25
+ 'authorization_header', 'auto_basic', 'auto_check_update', 'auto_play_seconds',
26
+ 'base_url', 'block', 'cache_control_disk_files', 'cert', 'comments_storage',
27
+ 'create-admin', 'debug', 'delete_unfinished_uploads_after', 'descript_ion',
28
+ 'descript_ion_encoding', 'disable_custom_html', 'disableTranslation', 'dont_log_net',
29
+ 'dont_overwrite_uploading', 'dynamic_dns_url', 'enable_plugins', 'error_log',
30
+ 'favicon', 'file_menu_on_link', 'file_timeout', 'folders_first', 'force_address',
31
+ 'force_https', 'force_lang', 'force_webdav_login',
32
+ 'geo_allow', 'geo_allow_unknown', 'geo_enable', 'geo_ignore_net', 'geo_list',
33
+ 'ignore_proxies', 'invert_order', 'keep_session_alive', 'listen_interface',
34
+ 'localhost_admin', 'log', 'log_api', 'log_gui', 'log_host', 'log_rotation', 'log_spam',
35
+ 'log_ua', 'mapped_port', 'max_downloads', 'max_downloads_per_account',
36
+ 'max_downloads_per_ip', 'max_kbps', 'max_kbps_per_ip', 'menu_at_top', 'mime',
37
+ 'min_available_mb', 'open_browser_at_start', 'outbound_interface', 'outbound_proxy', 'own_upload_delete_hours',
38
+ 'page_size', 'plugins_config', 'port', 'private_key', 'proxies', 'roots', 'server_code',
39
+ 'session_duration', 'show_hidden_files', 'show_uploader', 'size_1024',
40
+ 'smart_unc_folder_detection', 'sort_by', 'sort_numerics', 'split_uploads',
41
+ 'suspend_plugins', 'theme', 'tile_size', 'title', 'title_with_path', 'track_ips',
42
+ 'update_to_beta', 'upnp_enabled', 'version', 'vfs', 'webdav_initial_auth',
43
+ 'zip_calculate_size_for_seconds', 'https_port'
44
+ ]);
45
+ function constMap(a) {
46
+ return Object.fromEntries(a.map(x => [x, x]));
47
+ }
22
48
  exports.HTTP_OK = 200;
23
49
  exports.HTTP_CREATED = 201;
24
50
  exports.HTTP_NO_CONTENT = 204;
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_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;
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.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;
@@ -92,6 +92,7 @@ exports.safeDecodeURIComponent = safeDecodeURIComponent;
92
92
  exports.popKey = popKey;
93
93
  exports.patchKey = patchKey;
94
94
  exports.retry = retry;
95
+ exports.callAsPromise = callAsPromise;
95
96
  exports.toMutable = toMutable;
96
97
  exports.shortenAgent = shortenAgent;
97
98
  // This file is part of HFS - Copyright 2021-2023, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt
@@ -108,26 +109,21 @@ exports.HOUR = 60 * exports.MINUTE;
108
109
  exports.DAY = 24 * exports.HOUR;
109
110
  exports.MAX_TILE_SIZE = 10;
110
111
  exports.FRONTEND_OPTIONS = {
111
- file_menu_on_link: true,
112
- tile_size: 0,
113
- page_size: 100,
114
- sort_by: 'name',
115
- invert_order: false,
116
- folders_first: true,
117
- sort_numerics: false,
118
- title_with_path: true,
119
- theme: '',
120
- auto_play_seconds: 5,
121
- disableTranslation: false,
112
+ [cross_const_1.CFG.file_menu_on_link]: true,
113
+ [cross_const_1.CFG.menu_at_top]: false,
114
+ [cross_const_1.CFG.tile_size]: 0,
115
+ [cross_const_1.CFG.page_size]: 100,
116
+ [cross_const_1.CFG.sort_by]: 'name',
117
+ [cross_const_1.CFG.invert_order]: false,
118
+ [cross_const_1.CFG.folders_first]: true,
119
+ [cross_const_1.CFG.sort_numerics]: false,
120
+ [cross_const_1.CFG.title_with_path]: true,
121
+ [cross_const_1.CFG.theme]: '',
122
+ [cross_const_1.CFG.auto_play_seconds]: 5,
123
+ [cross_const_1.CFG.disableTranslation]: false,
122
124
  };
123
125
  exports.SORT_BY_OPTIONS = ['name', 'extension', 'size', 'time', 'creation'];
124
126
  exports.THEME_OPTIONS = { auto: '', light: 'light', dark: 'dark' };
125
- // had found an interesting way to infer a type from all the calls to defineConfig (by the literals passed), but would not be usable also by admin-panel
126
- exports.CFG = constMap(['geo_enable', 'geo_allow', 'geo_list', 'geo_allow_unknown', 'dynamic_dns_url',
127
- 'log', 'error_log', 'log_rotation', 'dont_log_net', 'log_gui', 'log_api', 'log_ua', 'log_spam', 'track_ips',
128
- 'max_downloads', 'max_downloads_per_ip', 'max_downloads_per_account', 'roots', 'force_address', 'split_uploads',
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', 'show_uploader']);
131
127
  exports.LIST = { add: '+', remove: '-', update: '=', props: 'props', ready: 'ready', error: 'e' };
132
128
  exports.WHO_ANYONE = true;
133
129
  exports.WHO_NO_ONE = false;
@@ -144,9 +140,6 @@ exports.defaultPerms = {
144
140
  exports.PERM_KEYS = typedKeys(exports.defaultPerms);
145
141
  exports.VFS_STORED_KEYS = ['name', 'source', 'masks', 'default', 'accept', 'rename',
146
142
  'mime', 'url', 'target', 'comment', 'icon', 'order', 'children', ...exports.PERM_KEYS];
147
- function constMap(a) {
148
- return Object.fromEntries(a.map(x => [x, x]));
149
- }
150
143
  function isWhoObject(v) {
151
144
  return v !== null && typeof v === 'object' && !Array.isArray(v);
152
145
  }
@@ -190,13 +183,13 @@ function haveTimeout(ms, job, error) {
190
183
  function objFromKeys(src, getValue) {
191
184
  return Object.fromEntries(src.map(k => [k, getValue(k)]));
192
185
  }
193
- function enforceFinal(sub, s, evenEmpty = false) {
186
+ function enforceFinal(sub, s = '', evenEmpty = false) {
194
187
  return (s ? !s.endsWith(sub) : evenEmpty) ? s + sub : s;
195
188
  }
196
189
  function removeFinal(sub, s) {
197
190
  return s.endsWith(sub) ? s.slice(0, -sub.length) : s;
198
191
  }
199
- function enforceStarting(sub, s, evenEmpty = false) {
192
+ function enforceStarting(sub, s = '', evenEmpty = false) {
200
193
  return (s ? !s.startsWith(sub) : evenEmpty) ? sub + s : s;
201
194
  }
202
195
  function removeStarting(sub, s) {
@@ -451,7 +444,7 @@ function xlate(input, table) {
451
444
  }
452
445
  // remove brackets and port (if any)
453
446
  function normalizeHost(host) {
454
- return host[0] === '[' ? host.slice(1, host.indexOf(']')) : host?.split(':')[0];
447
+ return host[0] === '[' ? host.slice(1, host.indexOf(']')) : host.split(':')[0];
455
448
  }
456
449
  function isIpLocalHost(ip) {
457
450
  return ip === '::1' || ip.endsWith('127.0.0.1');
@@ -565,16 +558,22 @@ function patchKey(o, k, replacer) {
565
558
  o[k] = replacer(o[k]);
566
559
  return o;
567
560
  }
568
- // consider the callback successful if it returns a truthy value
561
+ // retry rejected operations because filesystem resources may be released late
569
562
  async function retry(cb, delay = 1000) {
570
- let retry = 3;
571
- while (true) {
572
- if (await cb())
573
- break;
574
- if (!retry--)
575
- break;
576
- await wait(delay);
577
- }
563
+ let retries = 3;
564
+ while (true)
565
+ try {
566
+ return await cb();
567
+ }
568
+ catch (e) {
569
+ if (!retries--)
570
+ throw e;
571
+ await wait(delay);
572
+ }
573
+ }
574
+ // normalize callback results and synchronous throws into a promise
575
+ function callAsPromise(callback) {
576
+ return Promise.resolve().then(callback);
578
577
  }
579
578
  function toMutable(value) {
580
579
  return Array.isArray(value) ? value.slice() : { ...value };