hfs 3.2.2 → 3.3.0-beta2

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 (268) hide show
  1. package/admin/assets/AccountsPage-DB7ljNHT.js +3 -0
  2. package/admin/assets/ArrayField-Qj2iHD7I.js +2 -0
  3. package/admin/assets/ConfigFilePage-TErmt0PK.js +4 -0
  4. package/admin/assets/CustomHtmlPage-CVmk-_JS.js +2 -0
  5. package/admin/assets/DataTable-DmnQXyFO.js +119 -0
  6. package/admin/assets/DateTimeField-BUWd2w9-.js +1 -0
  7. package/admin/assets/EventManager-9vwKg8as.js +1 -0
  8. package/admin/assets/FileField-DoBaSTxF.js +117 -0
  9. package/admin/assets/InternetPage-Biuer1tb.js +8 -0
  10. package/admin/assets/LangPage-Vd24DNK5.js +2 -0
  11. package/admin/assets/LogsPage-BdvttEqM.js +3 -0
  12. package/admin/assets/MenuButton-CHLgqU5W.js +1 -0
  13. package/admin/assets/MonitorPage-DlXkZUmT.js +2 -0
  14. package/admin/assets/OptionsPage-Zi8FmrjE.js +3 -0
  15. package/admin/assets/PluginsPage-BzwEIFt5.js +1 -0
  16. package/admin/assets/Tabs-CVipqhB9.js +1 -0
  17. package/admin/assets/TextEditor-BeS-mR2I.css +1 -0
  18. package/admin/assets/TextEditor-clbVugYM.js +34 -0
  19. package/admin/assets/VfsPathField-YUCV9PfL.js +1 -0
  20. package/admin/assets/{af-BDoqNBMg.js → af-B1Gi_bpo.js} +1 -1
  21. package/admin/assets/agentIcons-DW9QViyx.js +1 -0
  22. package/admin/assets/{am-DXBvKJ3X.js → am-B-dZnhsM.js} +1 -1
  23. package/admin/assets/{ar-BJIoxKTh.js → ar-DsQGp6kZ.js} +1 -1
  24. package/admin/assets/{ar-dz-C9l3uc_3.js → ar-dz-CxlYiKtP.js} +1 -1
  25. package/admin/assets/{ar-iq-DKH8u2lX.js → ar-iq-y4_Puo4B.js} +1 -1
  26. package/admin/assets/{ar-kw-D3gJalQp.js → ar-kw-DTOZ0VC7.js} +1 -1
  27. package/admin/assets/{ar-ly-BZJzWFeR.js → ar-ly-lPV9gApv.js} +1 -1
  28. package/admin/assets/{ar-ma-DabiOCr0.js → ar-ma-DZqLSpeB.js} +1 -1
  29. package/admin/assets/{ar-sa-C7suzSCY.js → ar-sa-v-5vsIOR.js} +1 -1
  30. package/admin/assets/{ar-tn-BvBucEx0.js → ar-tn-a4q8lhXy.js} +1 -1
  31. package/admin/assets/{az-CPUppH2M.js → az-Cj4yq-j-.js} +1 -1
  32. package/admin/assets/baseUrl-Do5CYuBZ.js +2 -0
  33. package/admin/assets/{be-vzvhX2dl.js → be-B-3QlFM8.js} +1 -1
  34. package/admin/assets/{bg-09Xbj_Y3.js → bg-A8-93xdK.js} +1 -1
  35. package/admin/assets/{bi-CvhiuiE9.js → bi-bwwEWGG2.js} +1 -1
  36. package/admin/assets/{bm-DtJAZAkZ.js → bm-DpLmtYXC.js} +1 -1
  37. package/admin/assets/{bn-Dnzh2x6I.js → bn-DPL_BgLw.js} +1 -1
  38. package/admin/assets/{bn-bd-xEy4797g.js → bn-bd-DukmY_R5.js} +1 -1
  39. package/admin/assets/{bo-vuPmptG3.js → bo-68KDEKGI.js} +1 -1
  40. package/admin/assets/{br-C9xH4OD3.js → br-gA86uwqY.js} +1 -1
  41. package/admin/assets/{bs-BB_uCUp1.js → bs-SfthSp42.js} +1 -1
  42. package/admin/assets/{ca-D7LKaf98.js → ca-8qUtZKFe.js} +1 -1
  43. package/admin/assets/{cs-DMFTNdNC.js → cs-CTHcqPCX.js} +1 -1
  44. package/admin/assets/{cv-DNUjJwnO.js → cv-BhBW7hcO.js} +1 -1
  45. package/admin/assets/{cy-qmAOTPwC.js → cy-BYTS4PNY.js} +1 -1
  46. package/admin/assets/{da-DDX54iM9.js → da-BiwfOrRd.js} +1 -1
  47. package/admin/assets/{de-BBmlsfbM.js → de-BFqyDxvo.js} +1 -1
  48. package/admin/assets/{de-at-CuMZqYfu.js → de-at-C7zUXXVL.js} +1 -1
  49. package/admin/assets/{de-ch-s8OXKb-5.js → de-ch-BVHosAzt.js} +1 -1
  50. package/admin/assets/dist-DC2A6cD2.js +1 -0
  51. package/admin/assets/{dv-Cjs3QDBN.js → dv-dvig99nB.js} +1 -1
  52. package/admin/assets/{el-BsAOrUDP.js → el-BtJcYjoT.js} +1 -1
  53. package/admin/assets/en-B26lXzkx.js +1 -0
  54. package/admin/assets/{en-au-8tMHM2OT.js → en-au-CWzJ565h.js} +1 -1
  55. package/admin/assets/{en-ca-c9jM4M_8.js → en-ca-nou5bZ6q.js} +1 -1
  56. package/admin/assets/{en-in-D2ULzXMp.js → en-gb-kl3giCnh.js} +1 -1
  57. package/admin/assets/{en-ie-D4_e3kV6.js → en-ie-C55e3apX.js} +1 -1
  58. package/admin/assets/{en-il-BoaZOven.js → en-il-HYabE6j7.js} +1 -1
  59. package/admin/assets/{en-tt-Cn72OR4N.js → en-in-Bri93cNM.js} +1 -1
  60. package/admin/assets/{en-nz-grrmB3dg.js → en-nz-BqhpzeuX.js} +1 -1
  61. package/admin/assets/{en-sg-Cmqxpoir.js → en-sg-q8xv-C8V.js} +1 -1
  62. package/admin/assets/{en-gb-unFIXx9f.js → en-tt-BqzQXPA5.js} +1 -1
  63. package/admin/assets/{eo-Cqxmc0TT.js → eo-5Y962E4S.js} +1 -1
  64. package/admin/assets/{es-CkjKdFZ3.js → es-DOQ7swrW.js} +1 -1
  65. package/admin/assets/{es-do-CTcTshup.js → es-do-Diob7juh.js} +1 -1
  66. package/admin/assets/{es-mx-DMXgMMxs.js → es-mx-miy7RFDh.js} +1 -1
  67. package/admin/assets/{es-pr-DNCV91L3.js → es-pr-CZa5Iw2n.js} +1 -1
  68. package/admin/assets/{es-us-C7o2mlIH.js → es-us-BX03CBQf.js} +1 -1
  69. package/admin/assets/{esm-DvUbRuYx.js → esm-Dtaf0rWK.js} +1 -1
  70. package/admin/assets/{et-DaPpH-gK.js → et-DGWOmMlC.js} +1 -1
  71. package/admin/assets/{eu-DfKyFruu.js → eu-D1Qj8RMh.js} +1 -1
  72. package/admin/assets/{fa-xWrBjePb.js → fa--TfwKnl9.js} +1 -1
  73. package/admin/assets/{fi-BOWDMsDS.js → fi-Dcbkv925.js} +1 -1
  74. package/admin/assets/{fo-B3FupW8R.js → fo-BPGX_WF4.js} +1 -1
  75. package/admin/assets/{fr-DZBDBkwp.js → fr-CNsDHzNh.js} +1 -1
  76. package/admin/assets/{fr-ca-CRePPJJ5.js → fr-ca-BsNs3KIJ.js} +1 -1
  77. package/admin/assets/{fr-ch-pFKvYU8D.js → fr-ch-BBmdtvlB.js} +1 -1
  78. package/admin/assets/{fy-0k7RzHUH.js → fy-q3UE-mqp.js} +1 -1
  79. package/admin/assets/{ga-D31rB2ys.js → ga-C-DOgnYh.js} +1 -1
  80. package/admin/assets/{gd-CbgY-FFE.js → gd-mLMfry_l.js} +1 -1
  81. package/admin/assets/{gl-DrtvS9gt.js → gl-CO8Bj9I_.js} +1 -1
  82. package/admin/assets/{gom-latn-Dkke3GyE.js → gom-latn-myglMmHV.js} +1 -1
  83. package/admin/assets/{gu-BlEipYhR.js → gu-Bdz1OpMU.js} +1 -1
  84. package/admin/assets/{he-BrHT6OLB.js → he-C4lqk2AA.js} +1 -1
  85. package/admin/assets/{hi-Bx0zlAgr.js → hi-h0Byc_UN.js} +1 -1
  86. package/admin/assets/{hr-CoFYGbr6.js → hr-CZV7Za1n.js} +1 -1
  87. package/admin/assets/{ht-Df5xKo4a.js → ht-Dg2blNvl.js} +1 -1
  88. package/admin/assets/{hu-BFTN3q6-.js → hu-DbOVhd9K.js} +1 -1
  89. package/admin/assets/{hy-am-DuZTJQed.js → hy-am-E48Q7kWE.js} +1 -1
  90. package/admin/assets/icons-3rupj9Xr.js +1 -0
  91. package/admin/assets/{id-DBZ5nPkf.js → id-Cd4yWevW.js} +1 -1
  92. package/admin/assets/{index-eeRX25da.css → index-11n5z0Ez.css} +1 -1
  93. package/admin/assets/index-Dsq-sVu9.js +477 -0
  94. package/admin/assets/{is-CmeHm47B.js → is-BJZYSfrA.js} +1 -1
  95. package/admin/assets/{it-Cv3fjOA9.js → it-BRaqqFz4.js} +1 -1
  96. package/admin/assets/{it-ch-DCTyHA3T.js → it-ch-BkLqxaff.js} +1 -1
  97. package/admin/assets/{ja-DHNVXAM5.js → ja-X_I-25cD.js} +1 -1
  98. package/admin/assets/{jv-BdNIxhWv.js → jv-BBBx3R3v.js} +1 -1
  99. package/admin/assets/{ka-D-joD0QB.js → ka-D9Yrujqk.js} +1 -1
  100. package/admin/assets/{kk-8wRNNjxz.js → kk-Bz450_o8.js} +1 -1
  101. package/admin/assets/{km-dUWOg1uK.js → km-CKOAqvHM.js} +1 -1
  102. package/admin/assets/{kn-CwQbzel0.js → kn-k2xTBR7a.js} +1 -1
  103. package/admin/assets/{ko-D_gn9Sfw.js → ko-BCvoCYib.js} +1 -1
  104. package/admin/assets/{ku-Dh3MRML3.js → ku-D6Rloctr.js} +1 -1
  105. package/admin/assets/{ky-CWYuRvil.js → ky-C54qnEzk.js} +1 -1
  106. package/admin/assets/{lb-Cc2xsSIx.js → lb-y3rkrWB2.js} +1 -1
  107. package/admin/assets/{lo-CkwNFDBz.js → lo-nA_dPnOU.js} +1 -1
  108. package/admin/assets/{lt-HWyFDLXu.js → lt-hjPmCCML.js} +1 -1
  109. package/admin/assets/{lv-C7Zx3OLO.js → lv-Bh4qmUa_.js} +1 -1
  110. package/admin/assets/{me-DH3AqnfF.js → me-e32suC_n.js} +1 -1
  111. package/admin/assets/{mi-DMmUry6m.js → mi-Dwbsuyr9.js} +1 -1
  112. package/admin/assets/{mk-D6MNZrzX.js → mk-DjUXwKRn.js} +1 -1
  113. package/admin/assets/{ml-B_ur8Zqf.js → ml-CWjFAViF.js} +1 -1
  114. package/admin/assets/{mn-DeL8Ry7x.js → mn-BU4qTvEl.js} +1 -1
  115. package/admin/assets/{mr-C01qRXFL.js → mr-Bvnduzfb.js} +1 -1
  116. package/admin/assets/{ms-DFUZJzOm.js → ms-XuKVAni7.js} +1 -1
  117. package/admin/assets/{ms-my-CUYHkebs.js → ms-my-CDbnYp0g.js} +1 -1
  118. package/admin/assets/{mt-CE8t5jSj.js → mt-PDuxJf8J.js} +1 -1
  119. package/admin/assets/{my-Df96A8jZ.js → my-7N6SHnsD.js} +1 -1
  120. package/admin/assets/{nb-D0837ewu.js → nb-Cu3KUtTR.js} +1 -1
  121. package/admin/assets/{ne-BdK5TR0R.js → ne-CYW0uI2a.js} +1 -1
  122. package/admin/assets/{nl-oYKmSma6.js → nl-DL7cw699.js} +1 -1
  123. package/admin/assets/{nl-be-BfVC0cur.js → nl-be-B3NSF-2w.js} +1 -1
  124. package/admin/assets/{nn-IEUPnyc8.js → nn-BHITVkGB.js} +1 -1
  125. package/admin/assets/{oc-lnc-g_wSIROr.js → oc-lnc-dp3c4Bty.js} +1 -1
  126. package/admin/assets/{pa-in-gWhcLmZE.js → pa-in-BxgIlP1T.js} +1 -1
  127. package/admin/assets/{pl-H3-LoxVn.js → pl-GrG6yjkB.js} +1 -1
  128. package/admin/assets/{pt-br-B8UcNR6_.js → pt-br-BxTgeF95.js} +1 -1
  129. package/admin/assets/{pt-hiX8RqVw.js → pt-cVbj6hL6.js} +1 -1
  130. package/admin/assets/{rn-BKupViLr.js → rn-DiVJ2_Jy.js} +1 -1
  131. package/admin/assets/{ro-DmhgIvml.js → ro-D1WeRyXR.js} +1 -1
  132. package/admin/assets/rolldown-runtime-CacB07dV.js +1 -0
  133. package/admin/assets/routing-Cjyek7O_.js +1 -0
  134. package/admin/assets/{ru-CcpO-b8g.js → ru-DZCu4V9W.js} +1 -1
  135. package/admin/assets/{rw-DWX_5ZK0.js → rw-DfFpVc_g.js} +1 -1
  136. package/admin/assets/{sd-Dat8YgLw.js → sd-yDyBYxtQ.js} +1 -1
  137. package/admin/assets/{se-nTBYCwx3.js → se-EJKibocp.js} +1 -1
  138. package/admin/assets/sha512-DJtGRwVu.js +1 -0
  139. package/admin/assets/{si-DG88w5El.js → si-BCBV00rO.js} +1 -1
  140. package/admin/assets/{sk-s5FxW36J.js → sk-B2VvG_mo.js} +1 -1
  141. package/admin/assets/{sl-B-HyGrpA.js → sl-BTYmt65M.js} +1 -1
  142. package/admin/assets/{sq-DPFtfEC8.js → sq-BaLGKAHu.js} +1 -1
  143. package/admin/assets/{sr-bBPg1zz4.js → sr-DarE-95b.js} +1 -1
  144. package/admin/assets/{sr-cyrl-cXkYHMcP.js → sr-cyrl-CrwXFtkw.js} +1 -1
  145. package/admin/assets/{ss-7nSqXJN7.js → ss-CbBe1RUs.js} +1 -1
  146. package/admin/assets/{sv-B8mG1QK4.js → sv-Baw7Je9j.js} +1 -1
  147. package/admin/assets/{sv-fi-CotGRXSH.js → sv-fi-CVHORXkw.js} +1 -1
  148. package/admin/assets/{sw-fAxU-Uck.js → sw-k1H_OzIi.js} +1 -1
  149. package/admin/assets/{ta-Dl0Z1pan.js → ta-Bc4ZaZT9.js} +1 -1
  150. package/admin/assets/{te-DmcKasFF.js → te-ClciACZi.js} +1 -1
  151. package/admin/assets/{tet-D6mmvYpQ.js → tet-B75w1oSG.js} +1 -1
  152. package/admin/assets/{tg-39Hf2-oO.js → tg-CuH2ARRk.js} +1 -1
  153. package/admin/assets/{th-BSmTJSFN.js → th-LvExAoJ6.js} +1 -1
  154. package/admin/assets/{tk-m85E5igC.js → tk-2CFwTMKo.js} +1 -1
  155. package/admin/assets/{tl-ph-BlYxVvNt.js → tl-ph-CNJ_lViV.js} +1 -1
  156. package/admin/assets/{tlh-D1POnIeE.js → tlh-9UbKQkHo.js} +1 -1
  157. package/admin/assets/{tr-BurtfrLy.js → tr-Cu4P4OMe.js} +1 -1
  158. package/admin/assets/{tzl-BvyD0lOf.js → tzl-D97WsZW-.js} +1 -1
  159. package/admin/assets/{tzm-DZHS0dK6.js → tzm-CURkYgMY.js} +1 -1
  160. package/admin/assets/{tzm-latn-yNW7CNCH.js → tzm-latn-BRGsWK-9.js} +1 -1
  161. package/admin/assets/{ug-cn-rxs2pf5Y.js → ug-cn-Cd4exKOc.js} +1 -1
  162. package/admin/assets/{uk-CgZM-NnA.js → uk-DEXM3PIH.js} +1 -1
  163. package/admin/assets/{ur-YxIR8yYD.js → ur-DpUycj07.js} +1 -1
  164. package/admin/assets/{uz-CE9QsQMG.js → uz-CznjzufA.js} +1 -1
  165. package/admin/assets/{uz-latn-DiVdSW6G.js → uz-latn-Baga58FN.js} +1 -1
  166. package/admin/assets/{vi-DKARV6qf.js → vi-BrZWcL2C.js} +1 -1
  167. package/admin/assets/{x-pseudo-BuAdOrXQ.js → x-pseudo-BY6J4I7_.js} +1 -1
  168. package/admin/assets/{yo-BcB85DhC.js → yo-Dkeaym0x.js} +1 -1
  169. package/admin/assets/{zh-DLmxfxzl.js → zh-C2fOZUKR.js} +1 -1
  170. package/admin/assets/{zh-cn-ILRADZJi.js → zh-cn-CiA7hMZm.js} +1 -1
  171. package/admin/assets/{zh-hk-fT7fQk9_.js → zh-hk-EkoniWh0.js} +1 -1
  172. package/admin/assets/{zh-tw-NqRXGP6v.js → zh-tw-KDgos-kB.js} +1 -1
  173. package/admin/index.html +3 -2
  174. package/frontend/assets/{dist-legacy-BPToXNXu.js → dist-legacy-BNFW04Ss.js} +1 -1
  175. package/frontend/assets/index-legacy-2akkos6t.js +1 -0
  176. package/frontend/assets/{lib-legacy-CBk-_G90.js → lib-legacy-CO5spnaN.js} +1 -1
  177. package/frontend/assets/{sha512-legacy-CWrR2rkQ.js → sha512-legacy-BuJAJxVV.js} +1 -1
  178. package/frontend/index.html +1 -1
  179. package/npm-shrinkwrap.json +6 -6
  180. package/package.json +1 -1
  181. package/src/AsapStream.js +18 -14
  182. package/src/SendList.js +42 -16
  183. package/src/ThrottledStream.js +3 -3
  184. package/src/acme.js +23 -10
  185. package/src/adminApis.js +7 -6
  186. package/src/api.accounts.js +11 -7
  187. package/src/api.auth.js +8 -5
  188. package/src/api.cert.js +1 -1
  189. package/src/api.get_file_list.js +14 -11
  190. package/src/api.lang.js +3 -2
  191. package/src/api.monitor.js +1 -1
  192. package/src/api.net.js +3 -1
  193. package/src/api.plugins.js +2 -0
  194. package/src/api.vfs.js +9 -12
  195. package/src/apiMiddleware.js +5 -1
  196. package/src/basicWeb.js +3 -2
  197. package/src/block.js +1 -1
  198. package/src/commands.js +6 -5
  199. package/src/comments.js +29 -13
  200. package/src/config.js +7 -4
  201. package/src/consoleLog.js +14 -12
  202. package/src/const.js +1 -1
  203. package/src/cross-const.js +27 -1
  204. package/src/cross.js +37 -34
  205. package/src/ddns.js +1 -4
  206. package/src/events.js +1 -4
  207. package/src/fileAttr.js +1 -1
  208. package/src/frontEndApis.js +7 -3
  209. package/src/geo.js +8 -2
  210. package/src/github.js +10 -9
  211. package/src/index.js +2 -1
  212. package/src/ips.js +24 -3
  213. package/src/langs/hfs-lang-ar.json +1 -0
  214. package/src/langs/hfs-lang-bg.json +1 -0
  215. package/src/langs/hfs-lang-de.json +1 -0
  216. package/src/langs/hfs-lang-el.json +1 -0
  217. package/src/langs/hfs-lang-es.json +1 -0
  218. package/src/langs/hfs-lang-fi.json +1 -0
  219. package/src/langs/hfs-lang-fr.json +1 -0
  220. package/src/langs/hfs-lang-hu.json +1 -0
  221. package/src/langs/hfs-lang-it.json +1 -0
  222. package/src/langs/hfs-lang-ja.json +1 -0
  223. package/src/langs/hfs-lang-ko.json +1 -0
  224. package/src/langs/hfs-lang-lt.json +1 -0
  225. package/src/langs/hfs-lang-ms.json +1 -0
  226. package/src/langs/hfs-lang-nl.json +1 -0
  227. package/src/langs/hfs-lang-pl.json +1 -0
  228. package/src/langs/hfs-lang-pt-br.json +1 -0
  229. package/src/langs/hfs-lang-ro.json +1 -0
  230. package/src/langs/hfs-lang-ru.json +1 -0
  231. package/src/langs/hfs-lang-sr-latn.json +1 -0
  232. package/src/langs/hfs-lang-sr.json +1 -0
  233. package/src/langs/hfs-lang-th.json +1 -0
  234. package/src/langs/hfs-lang-tr.json +1 -0
  235. package/src/langs/hfs-lang-uk.json +1 -0
  236. package/src/langs/hfs-lang-vi.json +1 -0
  237. package/src/langs/hfs-lang-zh-tw.json +1 -0
  238. package/src/langs/hfs-lang-zh.json +1 -0
  239. package/src/listen.js +17 -13
  240. package/src/log.js +11 -2
  241. package/src/middlewares.js +4 -4
  242. package/src/misc.js +3 -3
  243. package/src/multipartUpload.js +1 -2
  244. package/src/nat.js +1 -1
  245. package/src/outboundProxy.js +7 -0
  246. package/src/perm.js +5 -3
  247. package/src/plugins.js +60 -37
  248. package/src/selfCheck.js +4 -3
  249. package/src/serveFile.js +3 -3
  250. package/src/serveGuiAndSharedFiles.js +7 -6
  251. package/src/serveGuiFiles.js +15 -2
  252. package/src/serverCode.js +27 -0
  253. package/src/throttler.js +3 -2
  254. package/src/update.js +9 -3
  255. package/src/upload.js +29 -11
  256. package/src/uploadOwners.js +95 -0
  257. package/src/util-files.js +4 -2
  258. package/src/util-http.js +3 -1
  259. package/src/vfs.js +67 -30
  260. package/src/walkDir.js +24 -11
  261. package/src/watchLoad.js +13 -2
  262. package/src/webdav.js +33 -31
  263. package/src/zip.js +1 -1
  264. package/admin/assets/dist-CHP3cW_n.js +0 -1
  265. package/admin/assets/en-DQ460ET7.js +0 -1
  266. package/admin/assets/index-BGIe1xaR.js +0 -760
  267. package/admin/assets/sha512-CZz40GwW.js +0 -1
  268. 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
  }
@@ -111,14 +112,16 @@ const get_file_list = async ({ uri = '/', offset, limit, c, onlyFolders, onlyFil
111
112
  const name = (0, vfs_1.getNodeName)(node);
112
113
  const isFolder = (0, vfs_1.nodeIsFolder)(node);
113
114
  try {
114
- const [web, comment, st] = await Promise.all([
115
- (0, vfs_1.getDefaultFile)(node, ctx).then(x => x ? true : undefined),
116
- node.comment ?? (0, comments_1.getCommentFor)(source),
117
- (0, vfs_1.nodeStats)(node).catch(e => {
118
- if (!isFolder || !node.children?.length) // folders with virtual children, keep them
119
- throw e;
120
- })
121
- ]);
115
+ const [web, comment, st] = node.see_without_probing
116
+ ? [undefined, node.comment, undefined]
117
+ : await Promise.all([
118
+ (0, vfs_1.getDefaultFile)(node, ctx).then(x => x ? true : undefined),
119
+ node.comment ?? (0, comments_1.getCommentFor)(source),
120
+ (0, vfs_1.nodeStats)(node).catch(e => {
121
+ if (!isFolder || !node.children?.length) // folders with virtual children, keep them
122
+ throw e;
123
+ })
124
+ ]);
122
125
  // permissions of entries are sent as a difference with permissions of parent
123
126
  const pl = node.can_list === misc_1.WHO_NO_ONE ? 'l'
124
127
  : !(0, vfs_1.hasPermission)(node, 'can_list', ctx) ? 'L'
@@ -146,7 +149,7 @@ const get_file_list = async ({ uri = '/', offset, limit, c, onlyFolders, onlyFil
146
149
  };
147
150
  }
148
151
  catch {
149
- return null;
152
+ return null; // ignore files with problems
150
153
  }
151
154
  function filesInsideCould(n = node) {
152
155
  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));
@@ -107,28 +107,24 @@ exports.default = {
107
107
  return {};
108
108
  },
109
109
  // legacy – not currently used by the UI
110
- async add_vfs({ parent, source, name, ...rest }) {
110
+ async add_vfs({ parent, source, name, skip_source_check, ...rest }) {
111
111
  if (!source && !name)
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))
119
119
  return new apiMiddleware_1.ApiError(const_1.HTTP_NOT_ACCEPTABLE, 'parent not a folder');
120
120
  if ((0, misc_1.isWindowsDrive)(source))
121
121
  source += '\\'; // slash must be included, otherwise it will refer to the cwd of that drive
122
- const isFolder = source && await (0, misc_1.isDirectory)(source);
123
- if (source && isFolder === undefined)
122
+ const isFolder = source && (skip_source_check ? (0, misc_1.hasFinalSlash)(source) : await (0, misc_1.isDirectory)(source));
123
+ if (source && !skip_source_check && isFolder === undefined)
124
124
  return new apiMiddleware_1.ApiError(const_1.HTTP_NOT_FOUND, 'source not found');
125
125
  const child = { source, name, ...sanitizeVfsProps(rest) };
126
126
  name = (0, vfs_1.getNodeName)(child); // could be not given as input
127
- const ext = (0, path_1.extname)(name);
128
- const noExt = ext ? name.slice(0, -ext.length) : name;
129
- let idx = 2;
130
- while (parentNode.children?.find((0, vfs_1.isSameFilenameAs)(name)))
131
- name = `${noExt} ${idx++}${ext}`;
127
+ name = (0, vfs_1.getFreeVfsName)(parentNode.children, name);
132
128
  child.name = name;
133
129
  simplifyName(child);
134
130
  (parentNode.children ||= []).unshift(child);
@@ -190,9 +186,10 @@ exports.default = {
190
186
  list.add({ n, k: 'd' });
191
187
  }
192
188
  catch (error) {
189
+ list.error(String(error));
193
190
  console.debug(error);
194
191
  }
195
- return;
192
+ return list.close();
196
193
  }
197
194
  const sendPropsAsap = (0, util_os_1.getDiskSpace)(path).then(x => x && list.props(x));
198
195
  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') {
@@ -41,7 +41,8 @@ function basicWeb(ctx, node) {
41
41
  const stream = (0, misc_1.asyncGeneratorToReadable)((0, misc_1.filterMapGenerator)(walker, async (el) => {
42
42
  const isFolder = (0, vfs_1.nodeIsFolder)(el);
43
43
  const name = (0, vfs_1.getNodeName)(el) + (isFolder ? '/' : '');
44
- return `<li>${a((0, misc_1.pathEncode)(name) + (isFolder && !await (0, vfs_1.getDefaultFile)(el, ctx) ? force : ''), name)}\n`;
44
+ const forceThis = isFolder && (el.see_without_probing || !await (0, vfs_1.getDefaultFile)(el, ctx)) ? force : '';
45
+ return `<li>${a((0, misc_1.pathEncode)(name) + forceThis, name)}\n`;
45
46
  }));
46
47
  ctx.body = stream;
47
48
  stream.push(`<meta name="viewport" content="width=device-width" />`);
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 => {
@@ -24,19 +28,17 @@ for (const k of ['log', 'warn', 'error', 'debug']) {
24
28
  const original = console[k];
25
29
  console[k] = (...args) => {
26
30
  const ts = new Date();
31
+ const msg = safeJoin(args); // if args contains a symbol, join will throw
27
32
  if (k === 'debug')
28
33
  args.unshift('DBG');
29
- else {
30
- const msg = safeJoin(args); // if args contains a symbol, join will throw
31
- const rec = { ts, k, msg };
32
- exports.consoleLog.push(rec);
33
- if (exports.consoleLog.length > 100_000) // limit to avoid infinite space
34
- exports.consoleLog.splice(0, 1_000);
35
- events_1.default.emit('console', rec);
36
- f?.write(`${(0, cross_1.formatTimestamp)(ts)} [${k}] ${msg}\n`);
37
- if (k !== 'log')
38
- args.unshift('!');
39
- }
34
+ else if (k !== 'log')
35
+ args.unshift('!');
36
+ const rec = { ts, k, msg };
37
+ exports.consoleLog.push(rec);
38
+ if (exports.consoleLog.length > 100_000) // limit to avoid infinite space
39
+ exports.consoleLog.splice(0, 1_000);
40
+ events_1.default.emit('console', rec);
41
+ f?.write(`${(0, cross_1.formatTimestamp)(ts)} [${k}] ${msg}\n`);
40
42
  if (!terminalOutputBroken) {
41
43
  try {
42
44
  return original((0, cross_1.formatTime)(ts), ...args);
package/src/const.js CHANGED
@@ -49,7 +49,7 @@ const path_1 = require("path");
49
49
  const cross_1 = require("./cross");
50
50
  const argv_1 = require("./argv");
51
51
  __exportStar(require("./cross-const"), exports);
52
- exports.API_VERSION = 13.1;
52
+ exports.API_VERSION = 13.2;
53
53
  exports.COMPATIBLE_API_VERSION = 1; // the day we break with the past, we'll update this
54
54
  // you can add arguments with this file, currently used for the update process on mac/linux.
55
55
  // we are using homedir because it's the only stable path both the old and new process can agree on (open() doesn't preserve cwd)
@@ -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;