hfs 3.2.1 → 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 (241) hide show
  1. package/admin/assets/{af-U8Sl5sCM.js → af-CevhRske.js} +1 -1
  2. package/admin/assets/{am-n9TdXja0.js → am-4ouVTwuo.js} +1 -1
  3. package/admin/assets/{ar-CUpxfuw5.js → ar-71cK8dbb.js} +1 -1
  4. package/admin/assets/{ar-dz-DC58Z9cj.js → ar-dz-CO7oh1gG.js} +1 -1
  5. package/admin/assets/{ar-iq-BDkrto6W.js → ar-iq-CjG6db8J.js} +1 -1
  6. package/admin/assets/{ar-kw-Bz806GvG.js → ar-kw-DMsdR225.js} +1 -1
  7. package/admin/assets/{ar-ly-DW7JaKir.js → ar-ly-ZhxIwUOA.js} +1 -1
  8. package/admin/assets/{ar-ma-BuveKbkq.js → ar-ma-Bo3Z1RyK.js} +1 -1
  9. package/admin/assets/{ar-sa-DRvob1Jp.js → ar-sa-Db44biae.js} +1 -1
  10. package/admin/assets/{ar-tn-Cae7pfui.js → ar-tn-DNWRixE0.js} +1 -1
  11. package/admin/assets/{az-B7PpjwCU.js → az-B6LcmVzB.js} +1 -1
  12. package/admin/assets/{be-DSy_sPdD.js → be-Dp0KhO5x.js} +1 -1
  13. package/admin/assets/{bg-Djacfatx.js → bg-TsOJJvBm.js} +1 -1
  14. package/admin/assets/{bi-DOLGYGEN.js → bi-DstczOGo.js} +1 -1
  15. package/admin/assets/{bm-BmR133he.js → bm-Bf09NN15.js} +1 -1
  16. package/admin/assets/{bn-DsjLUmm3.js → bn-Da3tHLgx.js} +1 -1
  17. package/admin/assets/{bn-bd-C3SafRtu.js → bn-bd-CeSalisC.js} +1 -1
  18. package/admin/assets/{bo-DsMRUw9A.js → bo-Ctxe0IKQ.js} +1 -1
  19. package/admin/assets/{br-BxR8Zm95.js → br-Y3dPq3Tj.js} +1 -1
  20. package/admin/assets/{bs-gH68uvCb.js → bs-BbAlSeZx.js} +1 -1
  21. package/admin/assets/{ca-DfGL180b.js → ca-8-Nn97gi.js} +1 -1
  22. package/admin/assets/{cs-D4lFVAq6.js → cs-BD5ArFNW.js} +1 -1
  23. package/admin/assets/{cv-CFyqoRN7.js → cv-CdMFVXGr.js} +1 -1
  24. package/admin/assets/{cy-DuubtylZ.js → cy-B3QEYlIT.js} +1 -1
  25. package/admin/assets/{da-Cp7c-T-B.js → da-Bju2YQ9G.js} +1 -1
  26. package/admin/assets/{de-CeQXU72n.js → de-DdzCTquR.js} +1 -1
  27. package/admin/assets/{de-at-Cx01BLTk.js → de-at-DgyKCkPl.js} +1 -1
  28. package/admin/assets/{de-ch-K5Vn_bWQ.js → de-ch-Dk9qkzvn.js} +1 -1
  29. package/admin/assets/{dist-CfPsVFTp.js → dist-KY_8edrw.js} +1 -1
  30. package/admin/assets/{dv-DB4pdLQj.js → dv-C5KWK8o4.js} +1 -1
  31. package/admin/assets/{el-CNA5lg_z.js → el-BDL_lPlM.js} +1 -1
  32. package/admin/assets/{en-Bpua-Wb5.js → en-Ds3H1Wec.js} +1 -1
  33. package/admin/assets/{en-au-CKwJ7DKC.js → en-au-CgltfNUb.js} +1 -1
  34. package/admin/assets/{en-ca-D8vKBgWW.js → en-ca-DEsKzbWO.js} +1 -1
  35. package/admin/assets/{en-in-Bhx4y1uu.js → en-gb-21AYC3Ik.js} +1 -1
  36. package/admin/assets/{en-ie-0gd4plad.js → en-ie-aN3g-yqK.js} +1 -1
  37. package/admin/assets/{en-il-BYIz25Pw.js → en-il-CZl2d7NK.js} +1 -1
  38. package/admin/assets/{en-tt-Bk-if7Re.js → en-in-C7Kv4c3w.js} +1 -1
  39. package/admin/assets/{en-nz-CtIJOjgh.js → en-nz-DA_sIaGR.js} +1 -1
  40. package/admin/assets/{en-sg-iygWx8GW.js → en-sg-CP5PJpZf.js} +1 -1
  41. package/admin/assets/{en-gb-C3jRv20Y.js → en-tt-B5vDLhL7.js} +1 -1
  42. package/admin/assets/{eo-DAvER5ej.js → eo-VE1KDD1u.js} +1 -1
  43. package/admin/assets/{es-C98a0Ggy.js → es-SUzi5oTU.js} +1 -1
  44. package/admin/assets/{es-do-TCakoZXN.js → es-do-Iri0eWm8.js} +1 -1
  45. package/admin/assets/{es-mx-BCj7c6Zn.js → es-mx-DwneKeOc.js} +1 -1
  46. package/admin/assets/{es-pr-rsR3Q8oM.js → es-pr-C4zPUdA1.js} +1 -1
  47. package/admin/assets/{es-us-DC9NM1XV.js → es-us-BPS67etc.js} +1 -1
  48. package/admin/assets/{esm-BQaMpob3.js → esm-CyNQ7bBW.js} +1 -1
  49. package/admin/assets/{et-tJ-d1AZX.js → et-Ciqbfae0.js} +1 -1
  50. package/admin/assets/{eu-xzUajtaj.js → eu-CGhOwQpe.js} +1 -1
  51. package/admin/assets/{fa-kR15gQkC.js → fa-BWZiAv_E.js} +1 -1
  52. package/admin/assets/{fi-DQjRBOgG.js → fi-DJmgkVsJ.js} +1 -1
  53. package/admin/assets/{fo-BHMXHU45.js → fo-DVez5N-g.js} +1 -1
  54. package/admin/assets/{fr-DGS71Nvo.js → fr-D206itJo.js} +1 -1
  55. package/admin/assets/{fr-ca-Bkzyy0tT.js → fr-ca-C6JiEHfi.js} +1 -1
  56. package/admin/assets/{fr-ch-BRrWrRWV.js → fr-ch-DUTEuSU_.js} +1 -1
  57. package/admin/assets/{fy-Dkv180q7.js → fy-C_r06zH-.js} +1 -1
  58. package/admin/assets/{ga-xsoXuVDY.js → ga-CrdQqAN5.js} +1 -1
  59. package/admin/assets/{gd-cLPVqYdG.js → gd-iWHfhKAH.js} +1 -1
  60. package/admin/assets/{gl-BUG0DHTt.js → gl-zgfy-pQ0.js} +1 -1
  61. package/admin/assets/{gom-latn-BKRncAa1.js → gom-latn-CMZ7Vj7V.js} +1 -1
  62. package/admin/assets/{gu-BpmXErqK.js → gu-DNqIMjZc.js} +1 -1
  63. package/admin/assets/{he-DNxd9A8z.js → he-OJBbYFzL.js} +1 -1
  64. package/admin/assets/{hi-dNFE6t75.js → hi-ByrMhKpX.js} +1 -1
  65. package/admin/assets/{hr-DNZiZtn4.js → hr-CfRZXzCu.js} +1 -1
  66. package/admin/assets/{ht-B1LoCeln.js → ht-DDrMtLzJ.js} +1 -1
  67. package/admin/assets/{hu-q6SYps3j.js → hu-Ckeq3R6_.js} +1 -1
  68. package/admin/assets/{hy-am-BLa2HL3i.js → hy-am-OpZy7Nvg.js} +1 -1
  69. package/admin/assets/{id-4zvXdXSK.js → id-BE8-Ys79.js} +1 -1
  70. package/admin/assets/{index-D2UH6ajh.js → index-34fRsXFH.js} +128 -123
  71. package/admin/assets/{is-B7wmPDNy.js → is-BOxJcH5N.js} +1 -1
  72. package/admin/assets/{it-CGm31WTp.js → it-BFlrIXvP.js} +1 -1
  73. package/admin/assets/{it-ch-HfVNoF64.js → it-ch-ILAp-lZZ.js} +1 -1
  74. package/admin/assets/{ja-RGFi1Jc2.js → ja-CbbYAYyn.js} +1 -1
  75. package/admin/assets/{jv-BclbE5yu.js → jv-CuLkRQCH.js} +1 -1
  76. package/admin/assets/{ka-D3ZrxVjT.js → ka-BFeRDN-I.js} +1 -1
  77. package/admin/assets/{kk-B7aIapNy.js → kk-CMvQxaPk.js} +1 -1
  78. package/admin/assets/{km-DPwsGner.js → km-BdQbDUNF.js} +1 -1
  79. package/admin/assets/{kn-CuXmm_DG.js → kn-CE5AS6Nx.js} +1 -1
  80. package/admin/assets/{ko-BlPgy9aF.js → ko-Ctl1mHOt.js} +1 -1
  81. package/admin/assets/{ku-B6w51rl_.js → ku-Blbo7opr.js} +1 -1
  82. package/admin/assets/{ky-RLmSGZmf.js → ky-iCha_cZX.js} +1 -1
  83. package/admin/assets/{lb-AKcfj05J.js → lb-BaP0S6h1.js} +1 -1
  84. package/admin/assets/{lo-VBKOzJn1.js → lo-B_Q4zUMa.js} +1 -1
  85. package/admin/assets/{lt-53JaHFcf.js → lt-BOaP1KGX.js} +1 -1
  86. package/admin/assets/{lv-DGOgmu9w.js → lv-S1XMSUX1.js} +1 -1
  87. package/admin/assets/{me-zNrvFUXX.js → me-ChOLVJHC.js} +1 -1
  88. package/admin/assets/{mi-BBDE1uK4.js → mi-CnoIUOGl.js} +1 -1
  89. package/admin/assets/{mk-2LiSrAS9.js → mk-BoEaxVXE.js} +1 -1
  90. package/admin/assets/{ml-BEwbrVql.js → ml-BuvmVn6P.js} +1 -1
  91. package/admin/assets/{mn-LuMGd2Ic.js → mn-2bvGd1HI.js} +1 -1
  92. package/admin/assets/{mr-C_dFhleR.js → mr-BAoAWQmy.js} +1 -1
  93. package/admin/assets/{ms-BHnmZJI4.js → ms-C2eT9r-f.js} +1 -1
  94. package/admin/assets/{ms-my-FcVPbGSE.js → ms-my-D2ZXdcAf.js} +1 -1
  95. package/admin/assets/{mt-DzhvOV5Z.js → mt-BL5JZC4T.js} +1 -1
  96. package/admin/assets/{my-Mmp3v0PH.js → my-CuJ4L0Ji.js} +1 -1
  97. package/admin/assets/{nb-CYPvI8e3.js → nb-CjE6cT80.js} +1 -1
  98. package/admin/assets/{ne-BtVsV5BN.js → ne-BL9I0cb1.js} +1 -1
  99. package/admin/assets/{nl-C6aRDFNr.js → nl-Dt3mtMln.js} +1 -1
  100. package/admin/assets/{nl-be-X-pQSY5N.js → nl-be-Am1mjZK9.js} +1 -1
  101. package/admin/assets/{nn-C1m9zYBT.js → nn-ChSewyYy.js} +1 -1
  102. package/admin/assets/{oc-lnc-CFT7xZs-.js → oc-lnc-x6_ChCE9.js} +1 -1
  103. package/admin/assets/{pa-in-Bby4AmI2.js → pa-in-DVyN8o2P.js} +1 -1
  104. package/admin/assets/{pl-MmIf2tXf.js → pl-CqcuAUGV.js} +1 -1
  105. package/admin/assets/{pt-DHCh_z9j.js → pt-B2BPpD7K.js} +1 -1
  106. package/admin/assets/{pt-br-Cu8EXyIK.js → pt-br-oBVRyUzP.js} +1 -1
  107. package/admin/assets/{rn-DZUJ5hf0.js → rn-B3e3nkfp.js} +1 -1
  108. package/admin/assets/{ro-BRHg1GDk.js → ro-C2tWFx0Q.js} +1 -1
  109. package/admin/assets/{ru-Wv8fMN5W.js → ru-CCsEUnFL.js} +1 -1
  110. package/admin/assets/{rw-B_XP1EHM.js → rw-CaCkWlGA.js} +1 -1
  111. package/admin/assets/{sd-_X3CAPlB.js → sd-90Zixcez.js} +1 -1
  112. package/admin/assets/{se-FItkWaOK.js → se-DaZXpLsK.js} +1 -1
  113. package/admin/assets/{sha512-UQMP5wnF.js → sha512-DlGRFb_l.js} +1 -1
  114. package/admin/assets/{si-DhVYEiDt.js → si-CSoBAkgi.js} +1 -1
  115. package/admin/assets/{sk-cWZR9gF4.js → sk-pgQaqa2z.js} +1 -1
  116. package/admin/assets/{sl-CJckZkV8.js → sl-Cr5kSdA7.js} +1 -1
  117. package/admin/assets/{sq-B7eoM8fS.js → sq-Pxi2MZOP.js} +1 -1
  118. package/admin/assets/{sr-SfiQA7kg.js → sr-Fdc9-lC-.js} +1 -1
  119. package/admin/assets/{sr-cyrl-Db8Rrl0U.js → sr-cyrl-06jNWqpg.js} +1 -1
  120. package/admin/assets/{ss-Bian2WDB.js → ss-BjEzSMit.js} +1 -1
  121. package/admin/assets/{sv-D-Dsf8uN.js → sv-CqWqUpcA.js} +1 -1
  122. package/admin/assets/{sv-fi-BBv1GPVJ.js → sv-fi-CsO2xQHd.js} +1 -1
  123. package/admin/assets/{sw-BIzZvuq6.js → sw-xQUdgYhn.js} +1 -1
  124. package/admin/assets/{ta-C2JoHqar.js → ta-epbFrZ1M.js} +1 -1
  125. package/admin/assets/{te-ByKFS-hT.js → te-DusTwU6q.js} +1 -1
  126. package/admin/assets/{tet-Bq9OQkFH.js → tet-ylc9v4gl.js} +1 -1
  127. package/admin/assets/{tg-BPqYCigR.js → tg-2QfVTAJf.js} +1 -1
  128. package/admin/assets/{th-m4ghaYsj.js → th-QIm0Mh7I.js} +1 -1
  129. package/admin/assets/{tk-CFxWTO9D.js → tk-140KMZeW.js} +1 -1
  130. package/admin/assets/{tl-ph-BiBY15io.js → tl-ph-S7IZkbx3.js} +1 -1
  131. package/admin/assets/{tlh-CyBfXfLa.js → tlh-C2fYb34-.js} +1 -1
  132. package/admin/assets/{tr-69QiF4Yn.js → tr-Be-IM6i4.js} +1 -1
  133. package/admin/assets/{tzl-BG9jvswA.js → tzl-DMy30zde.js} +1 -1
  134. package/admin/assets/{tzm-IJGtGn0k.js → tzm-Cu882cHO.js} +1 -1
  135. package/admin/assets/{tzm-latn-CAelnev_.js → tzm-latn-BYxpYHet.js} +1 -1
  136. package/admin/assets/{ug-cn-CKlXJ1Cr.js → ug-cn-Cl8Jqk2E.js} +1 -1
  137. package/admin/assets/{uk-DazXNZ9b.js → uk-B-8XymtC.js} +1 -1
  138. package/admin/assets/{ur-BFAjqt9p.js → ur-ChGuAlvQ.js} +1 -1
  139. package/admin/assets/{uz-CjoAOeqe.js → uz-QbfaW7fT.js} +1 -1
  140. package/admin/assets/{uz-latn-CzIzARA0.js → uz-latn-DIQivLCy.js} +1 -1
  141. package/admin/assets/{vi-DZuOT62g.js → vi-C2CmYlDu.js} +1 -1
  142. package/admin/assets/{x-pseudo-DlpsSPis.js → x-pseudo-BWgm1XlP.js} +1 -1
  143. package/admin/assets/{yo-BvI53ejW.js → yo-8bNbPP6u.js} +1 -1
  144. package/admin/assets/{zh-ci4y7UyJ.js → zh-CeNSRZ4r.js} +1 -1
  145. package/admin/assets/{zh-cn-CwOh6Lhp.js → zh-cn-BlbF5C_X.js} +1 -1
  146. package/admin/assets/{zh-hk-CPgMd5VF.js → zh-hk-kg1c_VSK.js} +1 -1
  147. package/admin/assets/{zh-tw-DJiWMOSA.js → zh-tw-BCl4UsPA.js} +1 -1
  148. package/admin/index.html +1 -1
  149. package/frontend/assets/{dist-legacy-CFPnugjA.js → dist-legacy-DYE1EOcc.js} +1 -1
  150. package/frontend/assets/index-legacy-Br-Lpsst.js +1 -0
  151. package/frontend/assets/{lib-legacy-CCULxbTk.js → lib-legacy-C4Gw_0kX.js} +1 -1
  152. package/frontend/assets/{sha512-legacy-D8zcvvqa.js → sha512-legacy-D3RbPSpl.js} +1 -1
  153. package/frontend/index.html +1 -1
  154. package/npm-shrinkwrap.json +16 -12
  155. package/package.json +6 -5
  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/auth.js +4 -1
  172. package/src/basicWeb.js +1 -1
  173. package/src/block.js +5 -4
  174. package/src/commands.js +6 -5
  175. package/src/comments.js +29 -13
  176. package/src/config.js +7 -4
  177. package/src/connections.js +3 -2
  178. package/src/consoleLog.js +5 -1
  179. package/src/cross-const.js +27 -1
  180. package/src/cross.js +32 -33
  181. package/src/ddns.js +1 -4
  182. package/src/events.js +1 -4
  183. package/src/fileAttr.js +6 -2
  184. package/src/frontEndApis.js +7 -3
  185. package/src/geo.js +8 -2
  186. package/src/github.js +10 -9
  187. package/src/index.js +1 -1
  188. package/src/ips.js +24 -3
  189. package/src/langs/embedded.js +2 -1
  190. package/src/langs/hfs-lang-ar.json +1 -0
  191. package/src/langs/hfs-lang-bg.json +1 -0
  192. package/src/langs/hfs-lang-de.json +1 -0
  193. package/src/langs/hfs-lang-el.json +1 -0
  194. package/src/langs/hfs-lang-es.json +1 -0
  195. package/src/langs/hfs-lang-fi.json +1 -0
  196. package/src/langs/hfs-lang-fr.json +1 -0
  197. package/src/langs/hfs-lang-hu.json +1 -0
  198. package/src/langs/hfs-lang-it.json +1 -0
  199. package/src/langs/hfs-lang-ja.json +1 -0
  200. package/src/langs/hfs-lang-ko.json +1 -0
  201. package/src/langs/hfs-lang-lt.json +1 -0
  202. package/src/langs/hfs-lang-ms.json +1 -0
  203. package/src/langs/hfs-lang-nl.json +1 -0
  204. package/src/langs/hfs-lang-pl.json +182 -0
  205. package/src/langs/hfs-lang-pt-br.json +1 -0
  206. package/src/langs/hfs-lang-ro.json +1 -0
  207. package/src/langs/hfs-lang-ru.json +1 -0
  208. package/src/langs/hfs-lang-sr-latn.json +1 -0
  209. package/src/langs/hfs-lang-sr.json +1 -0
  210. package/src/langs/hfs-lang-th.json +1 -0
  211. package/src/langs/hfs-lang-tr.json +1 -0
  212. package/src/langs/hfs-lang-uk.json +1 -0
  213. package/src/langs/hfs-lang-vi.json +1 -0
  214. package/src/langs/hfs-lang-zh-tw.json +1 -0
  215. package/src/langs/hfs-lang-zh.json +1 -0
  216. package/src/listen.js +18 -14
  217. package/src/log.js +11 -2
  218. package/src/middlewares.js +8 -6
  219. package/src/misc.js +3 -3
  220. package/src/multipartUpload.js +1 -2
  221. package/src/nat.js +5 -3
  222. package/src/outboundProxy.js +8 -0
  223. package/src/perm.js +5 -3
  224. package/src/plugins.js +58 -36
  225. package/src/selfCheck.js +8 -7
  226. package/src/serveFile.js +8 -6
  227. package/src/serveGuiAndSharedFiles.js +7 -6
  228. package/src/serveGuiFiles.js +13 -1
  229. package/src/serverCode.js +27 -0
  230. package/src/throttler.js +3 -2
  231. package/src/update.js +9 -3
  232. package/src/upload.js +30 -12
  233. package/src/uploadOwners.js +95 -0
  234. package/src/util-files.js +4 -2
  235. package/src/util-http.js +3 -1
  236. package/src/vfs.js +36 -28
  237. package/src/walkDir.js +24 -11
  238. package/src/watchLoad.js +13 -2
  239. package/src/webdav.js +16 -2
  240. package/src/zip.js +4 -3
  241. package/frontend/assets/index-legacy-CCOgiwQJ.js +0 -1
package/src/adminApis.js CHANGED
@@ -108,7 +108,7 @@ exports.adminApis = {
108
108
  });
109
109
  const res = await Promise.allSettled(ips.map(geo_1.ip2country));
110
110
  return {
111
- codes: res.map(x => x.status === 'rejected' || x.value === '-' ? '' : x.value)
111
+ codes: res.map(x => x.status === 'rejected' ? '' : x.value)
112
112
  };
113
113
  },
114
114
  is_ip_blocked({ ips }) {
@@ -167,7 +167,7 @@ exports.adminApis = {
167
167
  && await frpDebounced(),
168
168
  };
169
169
  },
170
- async add_block({ merge, ip, expire, comment }) {
170
+ add_block({ merge, ip, expire, comment }) {
171
171
  (0, misc_1.apiAssertTypes)({
172
172
  string: { ip },
173
173
  string_undefined: { comment, expire },
@@ -186,6 +186,7 @@ exports.adminApis = {
186
186
  return { result: Boolean((0, misc_1.try_)(() => (0, misc_1.makeNetMatcher)(mask))) };
187
187
  },
188
188
  };
189
+ // wrap every admin API so individual handlers cannot skip access control
189
190
  for (const [k, was] of (0, misc_1.typedEntries)(exports.adminApis))
190
191
  exports.adminApis[k] = ((params, ctx) => {
191
192
  if (ctxAdminAccess(ctx))
@@ -195,10 +196,10 @@ for (const [k, was] of (0, misc_1.typedEntries)(exports.adminApis))
195
196
  ? new SendList_1.SendListReadable({ doAtStart: x => x.error(const_1.HTTP_UNAUTHORIZED, true, props) })
196
197
  : new apiMiddleware_1.ApiError(const_1.HTTP_UNAUTHORIZED, props);
197
198
  });
198
- exports.localhostAdmin = (0, config_1.defineConfig)('localhost_admin', true);
199
- exports.adminNet = (0, config_1.defineConfig)('admin_net', '', v => (0, misc_1.makeNetMatcher)(v, true));
200
- exports.favicon = (0, config_1.defineConfig)('favicon', '');
201
- exports.title = (0, config_1.defineConfig)('title', "File server");
199
+ exports.localhostAdmin = (0, config_1.defineConfig)(misc_1.CFG.localhost_admin, true);
200
+ exports.adminNet = (0, config_1.defineConfig)(misc_1.CFG.admin_net, '', v => (0, misc_1.makeNetMatcher)(v, true));
201
+ exports.favicon = (0, config_1.defineConfig)(misc_1.CFG.favicon, '');
202
+ exports.title = (0, config_1.defineConfig)(misc_1.CFG.title, "File server");
202
203
  function ctxAdminAccess(ctx) {
203
204
  if (preventAdminAccess(ctx))
204
205
  return false;
@@ -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/auth.js CHANGED
@@ -106,7 +106,10 @@ async function setLoggedIn(ctx, username) {
106
106
  if (!a)
107
107
  return;
108
108
  await events_1.default.emitAsync('finalizingLogin', { ctx, username, inputs: { ...ctx.state.params, ...ctx.query } });
109
- s.username = (0, perm_1.normalizeUsername)(username);
109
+ const normalized = (0, perm_1.normalizeUsername)(username);
110
+ if (s.username !== normalized)
111
+ delete s.allowNet; // discard restrictions cached for another identity before replacing the session account
112
+ s.username = normalized;
110
113
  s.ts = Date.now();
111
114
  const k = cross_const_1.ALLOW_SESSION_IP_CHANGE;
112
115
  s[k] = k in ctx.query || Boolean(ctx.state.params?.[k]) || undefined; // login APIs will get ctx.state.params, others can rely on ctx.query
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,16 +13,17 @@ 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 => {
20
20
  rule.expire &&= new Date(rule.expire);
21
21
  return !rule.disabled && (rule.expire || now) >= now && (0, misc_1.makeNetMatcher)(rule.ip);
22
22
  }));
23
- // reapply new block to existing connections
24
- for (const { socket, ip } of (0, connections_1.getConnections)())
25
- applyBlock(socket, ip);
23
+ setTimeout(() => {
24
+ for (const { socket, ip } of (0, connections_1.getConnections)()) // reapply new block to existing connections
25
+ applyBlock(socket, ip);
26
+ });
26
27
  return ret;
27
28
  });
28
29
  function applyBlock(socket, ip = (0, connections_1.normalizeIp)(socket.remoteAddress || '')) {
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;
@@ -95,8 +95,9 @@ function disconnect(what, logMessage = '') {
95
95
  console.debug("Disconnection:", logMessage, ip);
96
96
  (0, geo_1.ip2country)(ip).then(res => {
97
97
  const rec = { ip, country: res || undefined, ts: new Date, msg: logMessage || undefined };
98
- exports.disconnectionsLog.unshift(rec);
99
- exports.disconnectionsLog.length = Math.min(1000, exports.disconnectionsLog.length);
98
+ exports.disconnectionsLog.push(rec);
99
+ if (exports.disconnectionsLog.length > 1000)
100
+ exports.disconnectionsLog.shift();
100
101
  events_1.default.emit('disconnection', rec);
101
102
  });
102
103
  return what.destroy();
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;