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
@@ -0,0 +1,95 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.uploadOwners = exports.ownUploadDeleteHours = void 0;
7
+ exports.setUploadOwner = setUploadOwner;
8
+ exports.moveUploadOwner = moveUploadOwner;
9
+ exports.deleteUploadOwner = deleteUploadOwner;
10
+ exports.getSessionId = getSessionId;
11
+ const kvstorage_1 = require("@rejetto/kvstorage");
12
+ const misc_1 = require("./misc");
13
+ const first_1 = require("./first");
14
+ const config_1 = require("./config");
15
+ const auth_1 = require("./auth");
16
+ const events_1 = __importDefault(require("./events"));
17
+ exports.ownUploadDeleteHours = (0, config_1.defineConfig)(misc_1.CFG.own_upload_delete_hours, 24);
18
+ exports.uploadOwners = new kvstorage_1.KvStorage({
19
+ rewriteLater: true,
20
+ });
21
+ exports.uploadOwners.open('upload-owners.kv').catch(e => console.error("Upload owners won't work correctly", e));
22
+ (0, first_1.onProcessExit)(() => exports.uploadOwners.close());
23
+ setInterval(() => {
24
+ if (!exports.uploadOwners.isOpen())
25
+ return;
26
+ const keys = Array.from(exports.uploadOwners.keys());
27
+ for (const k of keys) {
28
+ const owner = exports.uploadOwners.getSync(k);
29
+ if (owner && isExpired(owner))
30
+ void exports.uploadOwners.del(k);
31
+ }
32
+ }, misc_1.MINUTE);
33
+ events_1.default.on('checkVfsPermission', ({ node, perm, ctx }) => {
34
+ if (perm !== 'can_delete' || !node.source)
35
+ return;
36
+ const { vfsPath } = node;
37
+ if (!vfsPath || !exports.ownUploadDeleteHours.get() || !exports.uploadOwners.isOpen())
38
+ return;
39
+ const owner = exports.uploadOwners.getSync(cleanVfsPath(vfsPath));
40
+ if (!owner)
41
+ return;
42
+ if (isExpired(owner)) {
43
+ deleteUploadOwner(vfsPath);
44
+ return;
45
+ }
46
+ const username = (0, auth_1.getCurrentUsername)(ctx);
47
+ const { sessionId } = owner;
48
+ if (sessionId && sessionId === ctx.session?.sessionId || username && owner.username === username)
49
+ return 0;
50
+ });
51
+ function setUploadOwner(vfsPath, ctx) {
52
+ if (!exports.uploadOwners.isOpen() || !exports.ownUploadDeleteHours.get())
53
+ return;
54
+ const username = (0, auth_1.getCurrentUsername)(ctx) || undefined;
55
+ return exports.uploadOwners.put(cleanVfsPath(vfsPath), {
56
+ username,
57
+ sessionId: username ? undefined : getSessionId(ctx),
58
+ ip: ctx.ip,
59
+ created: new Date(),
60
+ })?.catch(e => {
61
+ console.error("Couldn't store upload owner for", vfsPath, String(e.message || e));
62
+ });
63
+ }
64
+ async function moveUploadOwner(fromPath, toPath) {
65
+ if (!exports.uploadOwners.isOpen())
66
+ return;
67
+ const from = cleanVfsPath(fromPath);
68
+ const affected = Array.from(exports.uploadOwners.keys()).filter(k => isSameOrInside(from, k));
69
+ if (!affected.length)
70
+ return;
71
+ const to = cleanVfsPath(toPath);
72
+ const owners = affected.map(k => ({ k, owner: exports.uploadOwners.getSync(k) }));
73
+ // ownership is keyed by VFS path, so HFS moves must carry descendant upload records too
74
+ await Promise.all(owners.map(({ k, owner }) => owner && exports.uploadOwners.put(to + k.slice(from.length), owner)));
75
+ await Promise.all(affected.map(k => exports.uploadOwners.del(k)));
76
+ }
77
+ function deleteUploadOwner(vfsPath) {
78
+ if (exports.uploadOwners.isOpen())
79
+ // deleting a folder must clear ownership for uploaded files below it too
80
+ for (const k of Array.from(exports.uploadOwners.keys()).filter(k => isSameOrInside(cleanVfsPath(vfsPath), k)))
81
+ void exports.uploadOwners.del(k);
82
+ }
83
+ function isExpired(owner) {
84
+ const hours = exports.ownUploadDeleteHours.get();
85
+ return !hours || Number(owner.created) + hours * misc_1.HOUR <= Date.now();
86
+ }
87
+ function getSessionId(ctx) {
88
+ return ctx.session.sessionId ||= (0, misc_1.randomId)(30);
89
+ }
90
+ function cleanVfsPath(path) {
91
+ return '/' + path.replace(/^\/+|\/+$/g, '');
92
+ }
93
+ function isSameOrInside(parent, path) {
94
+ return path === parent || path.startsWith(parent + '/');
95
+ }
package/src/util-files.js CHANGED
@@ -31,7 +31,7 @@ const const_1 = require("./const");
31
31
  const promises_2 = require("stream/promises");
32
32
  const stat_1 = require("./stat");
33
33
  const unzipper_1 = __importDefault(require("unzipper"));
34
- const fileTimeout = (0, config_1.defineConfig)('file_timeout', 3, x => x * 1000);
34
+ const fileTimeout = (0, config_1.defineConfig)(cross_1.CFG.file_timeout, 3, x => x * 1000);
35
35
  // a smart (and a bit arbitrary) way to decide if we need the stat-workers functionality. Without it, we may be a bit faster. We'll see with experience if we need a dedicated configuration.
36
36
  const disableStatWorkers = Number(process.env.UV_THREADPOOL_SIZE) >= 10;
37
37
  // some paths (mostly offline networked ones) can take 20+ seconds to fail
@@ -106,7 +106,9 @@ function escapeGlobPath(path) {
106
106
  async function unzip(stream, cb) {
107
107
  const extracted = new Map();
108
108
  let chain = Promise.resolve();
109
- return new Promise((resolve, reject) => stream.pipe(unzipper_1.default.Parse())
109
+ const parser = unzipper_1.default.Parse();
110
+ stream.once('error', e => parser.destroy(e)); // pipe doesn't forward source errors, so make the parser reject and release its resources
111
+ return new Promise((resolve, reject) => stream.pipe(parser)
110
112
  .on('close', () => chain.then(resolve, reject))
111
113
  .on('error', reject)
112
114
  .on('entry', (entry) => chain = chain.then(async () => {
package/src/util-http.js CHANGED
@@ -64,6 +64,7 @@ async function httpWithBody(url, options) {
64
64
  function httpStream(url, { body, proxy, jar, noRedirect, httpThrow = true, ...options } = {}, redirected = []) {
65
65
  const controller = new AbortController();
66
66
  options.signal ??= controller.signal;
67
+ options.localAddress ??= httpStream.defaultLocalAddress;
67
68
  return Object.assign(new Promise(async (resolve, reject) => {
68
69
  proxy ??= httpStream.defaultProxy;
69
70
  options.headers ??= {};
@@ -140,7 +141,7 @@ function httpStream(url, { body, proxy, jar, noRedirect, httpThrow = true, ...op
140
141
  if (options.timeout) // node only emits the timeout event, so destroy the request to unblock callers waiting for the body
141
142
  req.setTimeout(options.timeout, () => req.destroy(Object.assign(Error('timeout'), { code: 'ETIMEDOUT' })));
142
143
  if (body && body instanceof node_stream_1.Readable)
143
- body.pipe(req).on('end', () => req.end());
144
+ body.on('error', e => req.destroy(e)).pipe(req);
144
145
  else
145
146
  req.end(body);
146
147
  function connect() {
@@ -149,6 +150,7 @@ function httpStream(url, { body, proxy, jar, noRedirect, httpThrow = true, ...op
149
150
  (proxyParsed.protocol === 'https:' ? node_https_1.default : node_http_1.default).request({
150
151
  ...proxyParsed,
151
152
  auth: undefined, // void proxyParsed.auth
153
+ localAddress: options.localAddress, // the proxy socket is the actual outbound connection when tunneling HTTPS
152
154
  method: 'CONNECT',
153
155
  path,
154
156
  headers: { Host: path, ...proxyAuth }
package/src/vfs.js CHANGED
@@ -37,7 +37,12 @@ const comments_1 = require("./comments");
37
37
  const walkDir_1 = require("./walkDir");
38
38
  const node_stream_1 = require("node:stream");
39
39
  const adminApis_1 = require("./adminApis");
40
- const showHiddenFiles = (0, config_1.defineConfig)('show_hidden_files', false);
40
+ const showHiddenFiles = (0, config_1.defineConfig)(misc_1.CFG.show_hidden_files, false);
41
+ function setVfsPath(node, vfsPath, parent) {
42
+ return (0, misc_1.setHidden)(node, {
43
+ vfsPath: (0, misc_1.enforceFinal)('/', parent?.vfsPath) + (0, misc_1.pathEncode)(vfsPath)
44
+ });
45
+ }
41
46
  function permsFromParent(parent, child) {
42
47
  const ret = {};
43
48
  for (const k of misc_1.PERM_KEYS) {
@@ -81,20 +86,20 @@ function normalizeFilename(x) {
81
86
  return (const_1.IS_WINDOWS || const_1.IS_MAC ? x.toLocaleLowerCase() : x).normalize();
82
87
  }
83
88
  async function applyParentToChild(child, parent, name) {
84
- const ret = {
89
+ name ||= child ? getNodeName(child) : '';
90
+ const ret = setVfsPath({
85
91
  original: child, // this can be overridden by passing an 'original' in `child`
86
92
  ...child,
87
93
  isFolder: child?.isFolder ?? (child?.children?.length > 0 || undefined), // isFolder is hidden in original node, so we must copy it explicitly
88
94
  isTemp: true,
89
95
  parent,
90
- };
91
- name ||= child ? getNodeName(child) : '';
96
+ }, name, parent);
92
97
  inheritMasks(ret, parent, name);
93
98
  await parentMaskApplier(parent)(ret, name);
94
99
  inheritFromParent(ret);
95
100
  return ret;
96
101
  }
97
- async function urlToNode(url, ctx, parent = exports.vfs, allowMissing // true means missing path segments still resolve to temporary nodes with a computed source path
102
+ async function urlToNode(url, ctx, parent = exports.vfs.compiled(), allowMissing // true means missing path segments still resolve to temporary nodes with a computed source path
98
103
  ) {
99
104
  let initialSlashes = 0;
100
105
  while (url[initialSlashes] === '/')
@@ -112,6 +117,7 @@ async function urlToNode(url, ctx, parent = exports.vfs, allowMissing // true me
112
117
  const ret = await getNodeByName(name, parent, assumeFolder);
113
118
  if (!ret)
114
119
  return;
120
+ setVfsPath(ret, name, parent);
115
121
  if (rest || ret?.original)
116
122
  return urlToNode(rest, ctx, ret, allowMissing);
117
123
  if (ret.source)
@@ -164,7 +170,7 @@ async function getNodeByName(name, parent, assumeMissingToBeFolder = false) {
164
170
  return ret;
165
171
  }
166
172
  }
167
- const smartUncFolderDetection = (0, config_1.defineConfig)('smart_unc_folder_detection', false);
173
+ const smartUncFolderDetection = (0, config_1.defineConfig)(misc_1.CFG.smart_unc_folder_detection, false);
168
174
  async function setIsFolder(node) {
169
175
  if (!node.source)
170
176
  return;
@@ -174,26 +180,27 @@ async function setIsFolder(node) {
174
180
  (0, misc_1.setHidden)(node, { isFolder });
175
181
  return isFolder;
176
182
  }
177
- exports.vfs = {};
178
- (0, config_1.defineConfig)('vfs', exports.vfs).sub(async (x) => {
179
- await reviewVfs(x);
183
+ // compiled is the stable mutable tree, while get() may return a fresh clone of the default
184
+ exports.vfs = (0, config_1.defineConfig)(misc_1.CFG.vfs, {}, x => setVfsPath(structuredClone(x && typeof x === 'object' ? x : {}), '')); // ensure the type is right
185
+ exports.vfs.sub(async () => {
186
+ await reviewVfs();
180
187
  console.log('VFS ready');
181
188
  });
182
- async function reviewVfs(data = exports.vfs) {
189
+ async function reviewVfs() {
183
190
  await (async function recur(node) {
184
191
  if (node.source && !node.children?.length && node.isFolder === undefined)
185
192
  await setIsFolder(node);
186
193
  if (node.children)
187
194
  await Promise.allSettled(node.children.map(recur));
188
- })(data);
189
- exports.vfs = data;
195
+ })(exports.vfs.compiled());
190
196
  }
191
197
  exports.saveVfs = (0, misc_1.debounceAsync)(async () => {
192
- await reviewVfs();
193
- await (0, config_1.setConfig)({ vfs: exports.vfs }, true);
198
+ await reviewVfs(); // refresh runtime-derived folder flags before saving mutated VFS state
199
+ exports.vfs.set(exports.vfs.compiled()); // sync the mutable runtime tree back to config state before persisting it
200
+ (0, config_1.saveConfigAsap)();
194
201
  });
195
202
  function isRoot(node) {
196
- return node === exports.vfs;
203
+ return node === exports.vfs.compiled();
197
204
  }
198
205
  function getNodeName(node) {
199
206
  if (isRoot(node))
@@ -266,12 +273,9 @@ function statusCodeForMissingPerm(node, perm, ctx, assign = true) {
266
273
  } while (1);
267
274
  if ((0, misc_1.isWhoObject)(who) || isWhoVfsPerms(who))
268
275
  throw Error(`permission type-guard: ${JSON.stringify(who)}`);
269
- const eventName = 'checkVfsPermission';
270
- if (events_1.default.anyListener(eventName)) {
271
- const first = lodash_1.default.max(events_1.default.emit(eventName, { who, node, perm, ctx }));
272
- if (first !== undefined)
273
- return first;
274
- }
276
+ const first = lodash_1.default.max(events_1.default.emit('checkVfsPermission', { who, node, perm, ctx }));
277
+ if (first !== undefined)
278
+ return first;
275
279
  return simpleWhoToError(who, ctx)
276
280
  ?? (0, misc_1.throw_)(Error(`invalid permission: ${perm}=${(0, misc_1.try_)(() => JSON.stringify(who))}`));
277
281
  }
@@ -306,7 +310,7 @@ async function* walkNode(parent, { ctx, depth = Infinity, prefixPath = '', requi
306
310
  const nodeName = getNodeName(child);
307
311
  const name = prefixPath + nodeName;
308
312
  taken?.add(normalizeFilename(name));
309
- const item = { ...child, original: child, name, parent };
313
+ const item = setVfsPath({ ...child, original: child, name, parent }, name, parent);
310
314
  if (await cantSee(item))
311
315
  return;
312
316
  if (item.source && !item.children?.length) // real items must be accessible, unless there's more to it
@@ -349,7 +353,7 @@ async function* walkNode(parent, { ctx, depth = Infinity, prefixPath = '', requi
349
353
  const name = prefixPath + (renamed || path);
350
354
  if (taken?.has(normalizeFilename(name))) // taken by vfs node above
351
355
  return false; // false just in case it's a folder
352
- const item = { name, isFolder, source: (0, path_1.join)(source, path), parent, stats: entry.stats };
356
+ const item = setVfsPath({ name, isFolder, source: (0, path_1.join)(source, path), parent, stats: entry.stats }, name, parent);
353
357
  // masks containing '/' must be matched against the relative path while keeping walkDir recursion enabled
354
358
  await pathMaskApplier(item, renamed || path);
355
359
  if (await cantSee(item)) // can't see: don't produce and don't recur
@@ -482,13 +486,17 @@ events_1.default.on('accountRenamed', ({ from, to }) => {
482
486
  if (n.masks)
483
487
  Object.values(n.masks).forEach(renameInNode);
484
488
  n.children?.forEach(renameInNode);
485
- })(exports.vfs);
489
+ })(exports.vfs.compiled());
486
490
  (0, exports.saveVfs)();
487
491
  function renameInPerm(a) {
488
- if (!Array.isArray(a))
492
+ if ((0, misc_1.isWhoObject)(a)) {
493
+ renameInPerm(a.this);
494
+ renameInPerm(a.children);
489
495
  return;
490
- for (let i = 0; i < a.length; i++)
491
- if (a[i] === from)
492
- a[i] = to;
496
+ }
497
+ if (Array.isArray(a))
498
+ for (let i = 0; i < a.length; i++)
499
+ if (a[i] === from)
500
+ a[i] = to;
493
501
  }
494
502
  });
package/src/walkDir.js CHANGED
@@ -23,12 +23,12 @@ function walkDir(path, { depth = 0, hidden = true, parallelizeRecursion = false,
23
23
  return new Promise(async (resolve, reject) => {
24
24
  if (!await (0, util_files_1.isDirectory)(path))
25
25
  return reject(Error('ENOTDIR'));
26
- dirQ.add(() => readDir('', depth)
26
+ dirQ.add(() => readDir('', depth, [path])
27
27
  .then(res => {
28
28
  Promise.resolve(res?.branchDone).then(resolve);
29
29
  }, reject));
30
30
  });
31
- async function readDir(relativePath, depth) {
31
+ async function readDir(relativePath, depth, ancestorPaths) {
32
32
  if (stopped)
33
33
  return;
34
34
  const base = (0, path_1.join)(path, relativePath);
@@ -39,6 +39,8 @@ function walkDir(path, { depth = 0, hidden = true, parallelizeRecursion = false,
39
39
  const pluginReceiver = lodash_1.default.isFunction(res) && res || null;
40
40
  const pluginIterator = lodash_1.default.isFunction(res?.[Symbol.asyncIterator] || res?.[Symbol.iterator]) && res;
41
41
  if (const_1.IS_WINDOWS && !pluginIterator) { // use native apis to read the 'hidden' attribute
42
+ // fswin callbacks cannot await, so track their work before closing the branch
43
+ const entriesWorking = [];
42
44
  const direntMethods = {
43
45
  isDir: false,
44
46
  isFile() { return !this.isDir; },
@@ -46,14 +48,16 @@ function walkDir(path, { depth = 0, hidden = true, parallelizeRecursion = false,
46
48
  isBlockDevice() { return false; },
47
49
  isCharacterDevice() { return false; },
48
50
  };
49
- await new Promise(res => fswin_1.default.find(base + '\\*', (event, f) => {
50
- if (event !== 'FOUND')
51
- return res();
51
+ await new Promise((resolve, reject) => fswin_1.default.find(base + '\\*', (event, f) => {
52
+ if (event !== 'FOUND') {
53
+ Promise.all(entriesWorking).then(() => resolve());
54
+ return;
55
+ }
52
56
  if (stopped)
53
57
  return true; // stop signal
54
58
  if (!hidden && f.IS_HIDDEN)
55
59
  return;
56
- work(Object.assign(Object.create(direntMethods), {
60
+ entriesWorking.push(work(Object.assign(Object.create(direntMethods), {
57
61
  isDir: f.IS_DIRECTORY,
58
62
  name: f.LONG_NAME,
59
63
  stats: {
@@ -63,7 +67,7 @@ function walkDir(path, { depth = 0, hidden = true, parallelizeRecursion = false,
63
67
  isFile: () => !f.IS_DIRECTORY,
64
68
  isDirectory: () => f.IS_DIRECTORY,
65
69
  }
66
- }));
70
+ }), Boolean(f.REPARSE_POINT_TAG)).catch(reject));
67
71
  }, true));
68
72
  }
69
73
  else
@@ -72,7 +76,9 @@ function walkDir(path, { depth = 0, hidden = true, parallelizeRecursion = false,
72
76
  break;
73
77
  if (!hidden && !const_1.IS_WINDOWS && entry.name[0] === '.')
74
78
  continue;
75
- const stats = entry.isSymbolicLink?.() && await (0, util_files_1.statWithTimeout)((0, path_1.join)(base, entry.name)).catch(() => null);
79
+ // stat follows links, so preserve their identity for cycle detection
80
+ const isSymlink = entry.isSymbolicLink?.();
81
+ const stats = isSymlink && await (0, util_files_1.statWithTimeout)((0, path_1.join)(base, entry.name)).catch(() => null);
76
82
  if (stats === null)
77
83
  continue;
78
84
  if (stats)
@@ -80,7 +86,7 @@ function walkDir(path, { depth = 0, hidden = true, parallelizeRecursion = false,
80
86
  const expanded = entry;
81
87
  if (stats)
82
88
  expanded.stats = stats;
83
- await work(expanded);
89
+ await work(expanded, isSymlink);
84
90
  }
85
91
  pluginReceiver?.(!stopped);
86
92
  const branchDone = Promise.allSettled(subDirsDone);
@@ -90,7 +96,7 @@ function walkDir(path, { depth = 0, hidden = true, parallelizeRecursion = false,
90
96
  closingQ.push(relativePath); // ok, we'll ask next one to carry this info
91
97
  // don't return the promise directly, as this job ends here, but communicate to caller the promise for the whole branch
92
98
  return { branchDone, n };
93
- async function work(entry) {
99
+ async function work(entry, isSymlink = false) {
94
100
  entry.path = (relativePath && relativePath + '/') + entry.name;
95
101
  pluginReceiver?.(entry);
96
102
  if (last && closingQ.length) // pending entries
@@ -104,9 +110,16 @@ function walkDir(path, { depth = 0, hidden = true, parallelizeRecursion = false,
104
110
  n++;
105
111
  if (!depth || !entry.isDirectory())
106
112
  return;
113
+ const entryPath = (0, path_1.join)(base, entry.name);
114
+ if (isSymlink) {
115
+ // compare only with ancestors so separate links to the same folder still work
116
+ const [target, ...ancestors] = await Promise.all([entryPath, ...ancestorPaths].map(x => (0, promises_1.realpath)(x).catch(() => '')));
117
+ if (!target || ancestors.includes(target))
118
+ return;
119
+ }
107
120
  const branchDone = (0, cross_1.pendingPromise)(); // per-job
108
121
  subDirsDone.push(branchDone);
109
- const job = () => readDir(entry.path, depth - 1) // recur
122
+ const job = () => readDir(entry.path, depth - 1, [...ancestorPaths, entryPath]) // recur
110
123
  .then(x => x, () => { }) // mute errors
111
124
  .then(res => {
112
125
  if (!res?.n)
package/src/watchLoad.js CHANGED
@@ -12,8 +12,9 @@ const debounceAsync_1 = require("./debounceAsync");
12
12
  const events_1 = require("./events");
13
13
  function watchLoad(path, parser, { failedOnFirstAttempt, immediateFirst } = {}) {
14
14
  let doing = false;
15
+ let cancelled = false;
15
16
  let watcher;
16
- const debounced = (0, debounceAsync_1.debounceAsync)(load, { wait: 500, maxWait: 1000, reuseRunning: true });
17
+ const debounced = (0, debounceAsync_1.debounceAsync)(load, { wait: 500, maxWait: 1000, reuseRunning: true, cancelable: true });
17
18
  let retry;
18
19
  let last;
19
20
  const emitter = new events_1.BetterEventEmitter();
@@ -27,6 +28,8 @@ function watchLoad(path, parser, { failedOnFirstAttempt, immediateFirst } = {})
27
28
  install(true);
28
29
  return { unwatch, save, emitter, getText: () => last, getPath: () => path };
29
30
  function install(first = false) {
31
+ if (cancelled)
32
+ return;
30
33
  try {
31
34
  watcher = (0, fs_1.watch)(path, () => {
32
35
  if (!save.isWorking())
@@ -43,6 +46,12 @@ function watchLoad(path, parser, { failedOnFirstAttempt, immediateFirst } = {})
43
46
  }
44
47
  }
45
48
  function unwatch() {
49
+ // a pending or running load must not resurrect a watcher cancelled by its owner
50
+ cancelled = true;
51
+ debounced.cancel();
52
+ closeWatcher();
53
+ }
54
+ function closeWatcher() {
46
55
  watcher?.close();
47
56
  clearTimeout(retry);
48
57
  watcher = undefined;
@@ -61,12 +70,14 @@ function watchLoad(path, parser, { failedOnFirstAttempt, immediateFirst } = {})
61
70
  return '';
62
71
  throw e;
63
72
  });
73
+ if (cancelled)
74
+ return;
64
75
  if (text === last)
65
76
  return;
66
77
  last = text;
67
78
  emitter.emit('change', last);
68
79
  console.debug('Loaded', path);
69
- unwatch();
80
+ closeWatcher();
70
81
  install(); // reinstall, as the original file could have been renamed. We watch by the name.
71
82
  await parser(text);
72
83
  }
package/src/webdav.js CHANGED
@@ -24,6 +24,7 @@ const expiringCache_1 = require("./expiringCache");
24
24
  const fast_xml_parser_1 = require("fast-xml-parser");
25
25
  const lodash_1 = __importDefault(require("lodash"));
26
26
  const fileAttr_1 = require("./fileAttr");
27
+ const uploadOwners_1 = require("./uploadOwners");
27
28
  const forceWebdavLogin = (0, config_1.defineConfig)(cross_1.CFG.force_webdav_login, true, compileWebdavAgentRegex);
28
29
  const webdavInitialAuth = (0, config_1.defineConfig)(cross_1.CFG.webdav_initial_auth, 'WebDAVFS', compileWebdavAgentRegex);
29
30
  const webdavPrompted = (0, expiringCache_1.expiringCache)(cross_1.DAY);
@@ -151,6 +152,7 @@ const webdav = async (ctx, next) => {
151
152
  if (node?.source)
152
153
  await (0, promises_1.rm)(node.source)
153
154
  .then(() => (0, fileAttr_1.deleteStoredFileAttrs)(node.source))
155
+ .then(() => (0, uploadOwners_1.deleteUploadOwner)(path))
154
156
  .catch(() => { });
155
157
  }
156
158
  if (x && ctx.length === undefined) // missing length can make PUT fail
@@ -206,7 +208,7 @@ const webdav = async (ctx, next) => {
206
208
  if ((0, path_1.dirname)(path) === (0, path_1.dirname)(dest)) // rename case. `path` is is encoded, so we test before decoding `dest`
207
209
  try {
208
210
  // decode the single path segment so reserved chars like %2C become their real name on rename
209
- await (0, frontEndApis_1.requestedRename)(node, (0, cross_1.safeDecodeURIComponent)((0, path_1.basename)(dest), ''), ctx);
211
+ await (0, frontEndApis_1.requestedRename)(node, (0, cross_1.safeDecodeURIComponent)((0, path_1.basename)(dest), ''), ctx, path);
210
212
  releaseWebdavLock(path); // RFC 4918 says MOVE must not carry locks to destination, so clear source lock on success
211
213
  return ctx.status = cross_1.HTTP_CREATED;
212
214
  }
@@ -246,6 +248,18 @@ const webdav = async (ctx, next) => {
246
248
  }
247
249
  async function handleLock() {
248
250
  setWebdavHeaders();
251
+ // a lock reserves a future write, so authorize it against the existing resource or its parent
252
+ const node = await (0, vfs_1.urlToNode)(path, ctx);
253
+ const permissionNode = node || await (0, vfs_1.urlToNode)((0, path_1.dirname)(path), ctx);
254
+ if (!permissionNode)
255
+ return ctx.status = cross_1.HTTP_CONFLICT;
256
+ const missingWritePerm = node && canOverwrite.has(path + (0, cross_1.prefix)('|', (0, auth_1.getCurrentUsername)(ctx))) ? 0
257
+ : (0, vfs_1.statusCodeForMissingPerm)(permissionNode, node ? 'can_delete' : 'can_upload', ctx);
258
+ if (missingWritePerm) {
259
+ if (ctx.status === cross_1.HTTP_UNAUTHORIZED)
260
+ setWebdavHeaders(true);
261
+ return;
262
+ }
249
263
  const body = ctx.length || ctx.get('content-length') || ctx.get('transfer-encoding') ? await (0, consumers_1.text)(ctx.req) : '';
250
264
  const token = getProvidedLockToken();
251
265
  let seconds = Number(ctx.get('timeout').split(',').find(x => /^Second-\d+$/i.test(x.trim()))?.trim().split('-', 2)[1]);
@@ -321,13 +335,13 @@ const webdav = async (ctx, next) => {
321
335
  res.write(`<response>
322
336
  <href>${lodash_1.default.escape(outPath + (append ? (0, cross_1.pathEncode)(name, true) + (isDir ? '/' : '') : ''))}</href>
323
337
  <propstat>
324
- <status>HTTP/1.1 200 OK</status>
325
338
  <prop>
326
339
  ${(0, cross_1.prefix)('<getlastmodified>', st?.mtime?.toGMTString(), '</getlastmodified>')}
327
340
  ${(0, cross_1.prefix)('<creationdate>', (st?.birthtime || st?.ctime)?.toISOString().replace(/\..*/, '-00:00'), '</creationdate>')}
328
341
  ${isDir ? '<resourcetype><collection/></resourcetype>'
329
342
  : `<resourcetype/><getcontentlength>${st?.size}</getcontentlength>`}
330
343
  </prop>
344
+ <status>HTTP/1.1 200 OK</status>
331
345
  </propstat>
332
346
  </response>
333
347
  `);
package/src/zip.js CHANGED
@@ -39,8 +39,9 @@ async function zipStreamFromFolder(node, ctx) {
39
39
  continue;
40
40
  if ((0, vfs_1.nodeIsFolder)(subNode)) { // a directory needs to be walked
41
41
  if ((0, vfs_1.hasPermission)(subNode, 'can_list', ctx) && (0, vfs_1.hasPermission)(subNode, 'can_archive', ctx)) {
42
- yield subNode; // it could be empty
43
- yield* (0, vfs_1.walkNode)(subNode, { ctx, prefixPath: (0, misc_1.pathDecodeSegments)(uri) + '/', requiredPerm: 'can_archive' });
42
+ const archivePath = (0, misc_1.pathDecodeSegments)(uri);
43
+ yield { ...subNode, name: archivePath }; // keep empty selected folders at the same path used for their contents
44
+ yield* (0, vfs_1.walkNode)(subNode, { ctx, prefixPath: archivePath + '/', requiredPerm: 'can_archive' });
44
45
  }
45
46
  continue;
46
47
  }
@@ -93,4 +94,4 @@ async function zipStreamFromFolder(node, ctx) {
93
94
  ctx.state.archive = 'zip';
94
95
  (0, serveFile_1.monitorAsDownload)(ctx, size, range?.start);
95
96
  }
96
- const zipSeconds = (0, config_1.defineConfig)('zip_calculate_size_for_seconds', 5);
97
+ const zipSeconds = (0, config_1.defineConfig)(misc_1.CFG.zip_calculate_size_for_seconds, 5);