hfs 3.0.7 → 3.1.0-alpha2

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 (317) hide show
  1. package/README.md +1 -1
  2. package/admin/assets/index-CDiYkO8j.css +1 -0
  3. package/admin/assets/index-Dm5BN1iZ.js +1 -0
  4. package/admin/assets/index-P5i8LyWl.js +822 -0
  5. package/admin/assets/{sha512-gDcjPgJS.js → sha512-DG-ElrDe.js} +1 -1
  6. package/admin/flags/freakflags.css +284 -0
  7. package/admin/flags/sprite.png +0 -0
  8. package/admin/index.html +3 -2
  9. package/frontend/assets/index-legacy-CG_og8xL.js +1 -0
  10. package/frontend/assets/index-legacy-DDLKWGcm.js +9 -0
  11. package/frontend/assets/{sha512-legacy-BBwwadDl.js → sha512-legacy-D1zEZzDe.js} +1 -1
  12. package/frontend/fontello.css +3 -1
  13. package/frontend/fontello.woff2 +0 -0
  14. package/frontend/index.html +1 -1
  15. package/npm-shrinkwrap.json +16409 -0
  16. package/package.json +12 -11
  17. package/plugins/antibrute/plugin.js +14 -3
  18. package/src/AsapStream.js +49 -0
  19. package/src/ThrottledStream.js +1 -1
  20. package/src/adminApis.js +16 -5
  21. package/src/api.accounts.js +1 -6
  22. package/src/api.auth.js +103 -100
  23. package/src/api.get_file_list.js +7 -0
  24. package/src/api.lang.js +1 -0
  25. package/src/api.log.js +5 -3
  26. package/src/api.monitor.js +5 -0
  27. package/src/api.net.js +5 -0
  28. package/src/api.plugins.js +13 -2
  29. package/src/api.vfs.js +41 -28
  30. package/src/apiMiddleware.js +5 -4
  31. package/src/auth.js +39 -6
  32. package/src/commands.js +16 -27
  33. package/src/comments.js +4 -4
  34. package/src/config.js +5 -2
  35. package/src/const.js +2 -16
  36. package/src/cross-const.js +5 -2
  37. package/src/cross.js +21 -15
  38. package/src/expiringCache.js +16 -5
  39. package/src/fileAttr.js +1 -1
  40. package/src/first.js +7 -4
  41. package/src/frontEndApis.js +88 -102
  42. package/src/github.js +32 -21
  43. package/src/ips.js +1 -1
  44. package/src/log.js +5 -3
  45. package/src/middlewares.js +2 -1
  46. package/src/misc.js +2 -20
  47. package/src/perm.js +27 -12
  48. package/src/persistence.js +1 -1
  49. package/src/plugins.js +12 -6
  50. package/src/serveFile.js +3 -3
  51. package/src/serveGuiAndSharedFiles.js +3 -0
  52. package/src/serveGuiFiles.js +2 -1
  53. package/src/srp.js +5 -6
  54. package/src/stat.js +4 -1
  55. package/src/update.js +18 -25
  56. package/src/upload.js +4 -3
  57. package/src/util-files.js +2 -1
  58. package/src/util-http.js +3 -3
  59. package/src/vfs.js +4 -2
  60. package/src/webdav.js +297 -0
  61. package/admin/assets/index-Bj9Dk3JN.js +0 -822
  62. package/admin/assets/index-Byv1_NxP.css +0 -1
  63. package/admin/flags/ad.png +0 -0
  64. package/admin/flags/ae.png +0 -0
  65. package/admin/flags/af.png +0 -0
  66. package/admin/flags/ag.png +0 -0
  67. package/admin/flags/ai.png +0 -0
  68. package/admin/flags/al.png +0 -0
  69. package/admin/flags/am.png +0 -0
  70. package/admin/flags/ao.png +0 -0
  71. package/admin/flags/aq.png +0 -0
  72. package/admin/flags/ar.png +0 -0
  73. package/admin/flags/as.png +0 -0
  74. package/admin/flags/at.png +0 -0
  75. package/admin/flags/au.png +0 -0
  76. package/admin/flags/aw.png +0 -0
  77. package/admin/flags/ax.png +0 -0
  78. package/admin/flags/az.png +0 -0
  79. package/admin/flags/ba.png +0 -0
  80. package/admin/flags/bb.png +0 -0
  81. package/admin/flags/bd.png +0 -0
  82. package/admin/flags/be.png +0 -0
  83. package/admin/flags/bf.png +0 -0
  84. package/admin/flags/bg.png +0 -0
  85. package/admin/flags/bh.png +0 -0
  86. package/admin/flags/bi.png +0 -0
  87. package/admin/flags/bj.png +0 -0
  88. package/admin/flags/bl.png +0 -0
  89. package/admin/flags/bm.png +0 -0
  90. package/admin/flags/bn.png +0 -0
  91. package/admin/flags/bo.png +0 -0
  92. package/admin/flags/bq.png +0 -0
  93. package/admin/flags/br.png +0 -0
  94. package/admin/flags/bs.png +0 -0
  95. package/admin/flags/bt.png +0 -0
  96. package/admin/flags/bv.png +0 -0
  97. package/admin/flags/bw.png +0 -0
  98. package/admin/flags/by.png +0 -0
  99. package/admin/flags/bz.png +0 -0
  100. package/admin/flags/ca.png +0 -0
  101. package/admin/flags/cc.png +0 -0
  102. package/admin/flags/cd.png +0 -0
  103. package/admin/flags/cf.png +0 -0
  104. package/admin/flags/cg.png +0 -0
  105. package/admin/flags/ch.png +0 -0
  106. package/admin/flags/ci.png +0 -0
  107. package/admin/flags/ck.png +0 -0
  108. package/admin/flags/cl.png +0 -0
  109. package/admin/flags/cm.png +0 -0
  110. package/admin/flags/cn.png +0 -0
  111. package/admin/flags/co.png +0 -0
  112. package/admin/flags/cr.png +0 -0
  113. package/admin/flags/cu.png +0 -0
  114. package/admin/flags/cv.png +0 -0
  115. package/admin/flags/cw.png +0 -0
  116. package/admin/flags/cx.png +0 -0
  117. package/admin/flags/cy.png +0 -0
  118. package/admin/flags/cz.png +0 -0
  119. package/admin/flags/de.png +0 -0
  120. package/admin/flags/dj.png +0 -0
  121. package/admin/flags/dk.png +0 -0
  122. package/admin/flags/dm.png +0 -0
  123. package/admin/flags/do.png +0 -0
  124. package/admin/flags/dz.png +0 -0
  125. package/admin/flags/ec.png +0 -0
  126. package/admin/flags/ee.png +0 -0
  127. package/admin/flags/eg.png +0 -0
  128. package/admin/flags/eh.png +0 -0
  129. package/admin/flags/er.png +0 -0
  130. package/admin/flags/es.png +0 -0
  131. package/admin/flags/et.png +0 -0
  132. package/admin/flags/fi.png +0 -0
  133. package/admin/flags/fj.png +0 -0
  134. package/admin/flags/fk.png +0 -0
  135. package/admin/flags/fm.png +0 -0
  136. package/admin/flags/fo.png +0 -0
  137. package/admin/flags/fr.png +0 -0
  138. package/admin/flags/ga.png +0 -0
  139. package/admin/flags/gb-eng.png +0 -0
  140. package/admin/flags/gb-nir.png +0 -0
  141. package/admin/flags/gb-sct.png +0 -0
  142. package/admin/flags/gb-wls.png +0 -0
  143. package/admin/flags/gb.png +0 -0
  144. package/admin/flags/gd.png +0 -0
  145. package/admin/flags/ge.png +0 -0
  146. package/admin/flags/gf.png +0 -0
  147. package/admin/flags/gg.png +0 -0
  148. package/admin/flags/gh.png +0 -0
  149. package/admin/flags/gi.png +0 -0
  150. package/admin/flags/gl.png +0 -0
  151. package/admin/flags/gm.png +0 -0
  152. package/admin/flags/gn.png +0 -0
  153. package/admin/flags/gp.png +0 -0
  154. package/admin/flags/gq.png +0 -0
  155. package/admin/flags/gr.png +0 -0
  156. package/admin/flags/gs.png +0 -0
  157. package/admin/flags/gt.png +0 -0
  158. package/admin/flags/gu.png +0 -0
  159. package/admin/flags/gw.png +0 -0
  160. package/admin/flags/gy.png +0 -0
  161. package/admin/flags/hk.png +0 -0
  162. package/admin/flags/hm.png +0 -0
  163. package/admin/flags/hn.png +0 -0
  164. package/admin/flags/hr.png +0 -0
  165. package/admin/flags/ht.png +0 -0
  166. package/admin/flags/hu.png +0 -0
  167. package/admin/flags/id.png +0 -0
  168. package/admin/flags/ie.png +0 -0
  169. package/admin/flags/il.png +0 -0
  170. package/admin/flags/im.png +0 -0
  171. package/admin/flags/in.png +0 -0
  172. package/admin/flags/io.png +0 -0
  173. package/admin/flags/iq.png +0 -0
  174. package/admin/flags/ir.png +0 -0
  175. package/admin/flags/is.png +0 -0
  176. package/admin/flags/it.png +0 -0
  177. package/admin/flags/je.png +0 -0
  178. package/admin/flags/jm.png +0 -0
  179. package/admin/flags/jo.png +0 -0
  180. package/admin/flags/jp.png +0 -0
  181. package/admin/flags/ke.png +0 -0
  182. package/admin/flags/kg.png +0 -0
  183. package/admin/flags/kh.png +0 -0
  184. package/admin/flags/ki.png +0 -0
  185. package/admin/flags/km.png +0 -0
  186. package/admin/flags/kn.png +0 -0
  187. package/admin/flags/kp.png +0 -0
  188. package/admin/flags/kr.png +0 -0
  189. package/admin/flags/kw.png +0 -0
  190. package/admin/flags/ky.png +0 -0
  191. package/admin/flags/kz.png +0 -0
  192. package/admin/flags/la.png +0 -0
  193. package/admin/flags/lb.png +0 -0
  194. package/admin/flags/lc.png +0 -0
  195. package/admin/flags/li.png +0 -0
  196. package/admin/flags/lk.png +0 -0
  197. package/admin/flags/lr.png +0 -0
  198. package/admin/flags/ls.png +0 -0
  199. package/admin/flags/lt.png +0 -0
  200. package/admin/flags/lu.png +0 -0
  201. package/admin/flags/lv.png +0 -0
  202. package/admin/flags/ly.png +0 -0
  203. package/admin/flags/ma.png +0 -0
  204. package/admin/flags/mc.png +0 -0
  205. package/admin/flags/md.png +0 -0
  206. package/admin/flags/me.png +0 -0
  207. package/admin/flags/mf.png +0 -0
  208. package/admin/flags/mg.png +0 -0
  209. package/admin/flags/mh.png +0 -0
  210. package/admin/flags/mk.png +0 -0
  211. package/admin/flags/ml.png +0 -0
  212. package/admin/flags/mm.png +0 -0
  213. package/admin/flags/mn.png +0 -0
  214. package/admin/flags/mo.png +0 -0
  215. package/admin/flags/mp.png +0 -0
  216. package/admin/flags/mq.png +0 -0
  217. package/admin/flags/mr.png +0 -0
  218. package/admin/flags/ms.png +0 -0
  219. package/admin/flags/mt.png +0 -0
  220. package/admin/flags/mu.png +0 -0
  221. package/admin/flags/mv.png +0 -0
  222. package/admin/flags/mw.png +0 -0
  223. package/admin/flags/mx.png +0 -0
  224. package/admin/flags/my.png +0 -0
  225. package/admin/flags/mz.png +0 -0
  226. package/admin/flags/na.png +0 -0
  227. package/admin/flags/nc.png +0 -0
  228. package/admin/flags/ne.png +0 -0
  229. package/admin/flags/nf.png +0 -0
  230. package/admin/flags/ng.png +0 -0
  231. package/admin/flags/ni.png +0 -0
  232. package/admin/flags/nl.png +0 -0
  233. package/admin/flags/no.png +0 -0
  234. package/admin/flags/np.png +0 -0
  235. package/admin/flags/nr.png +0 -0
  236. package/admin/flags/nu.png +0 -0
  237. package/admin/flags/nz.png +0 -0
  238. package/admin/flags/om.png +0 -0
  239. package/admin/flags/pa.png +0 -0
  240. package/admin/flags/pe.png +0 -0
  241. package/admin/flags/pf.png +0 -0
  242. package/admin/flags/pg.png +0 -0
  243. package/admin/flags/ph.png +0 -0
  244. package/admin/flags/pk.png +0 -0
  245. package/admin/flags/pl.png +0 -0
  246. package/admin/flags/pm.png +0 -0
  247. package/admin/flags/pn.png +0 -0
  248. package/admin/flags/pr.png +0 -0
  249. package/admin/flags/ps.png +0 -0
  250. package/admin/flags/pt.png +0 -0
  251. package/admin/flags/pw.png +0 -0
  252. package/admin/flags/py.png +0 -0
  253. package/admin/flags/qa.png +0 -0
  254. package/admin/flags/re.png +0 -0
  255. package/admin/flags/ro.png +0 -0
  256. package/admin/flags/rs.png +0 -0
  257. package/admin/flags/ru.png +0 -0
  258. package/admin/flags/rw.png +0 -0
  259. package/admin/flags/sa.png +0 -0
  260. package/admin/flags/sb.png +0 -0
  261. package/admin/flags/sc.png +0 -0
  262. package/admin/flags/sd.png +0 -0
  263. package/admin/flags/se.png +0 -0
  264. package/admin/flags/sg.png +0 -0
  265. package/admin/flags/sh.png +0 -0
  266. package/admin/flags/si.png +0 -0
  267. package/admin/flags/sj.png +0 -0
  268. package/admin/flags/sk.png +0 -0
  269. package/admin/flags/sl.png +0 -0
  270. package/admin/flags/sm.png +0 -0
  271. package/admin/flags/sn.png +0 -0
  272. package/admin/flags/so.png +0 -0
  273. package/admin/flags/sr.png +0 -0
  274. package/admin/flags/ss.png +0 -0
  275. package/admin/flags/st.png +0 -0
  276. package/admin/flags/sv.png +0 -0
  277. package/admin/flags/sx.png +0 -0
  278. package/admin/flags/sy.png +0 -0
  279. package/admin/flags/sz.png +0 -0
  280. package/admin/flags/tc.png +0 -0
  281. package/admin/flags/td.png +0 -0
  282. package/admin/flags/tf.png +0 -0
  283. package/admin/flags/tg.png +0 -0
  284. package/admin/flags/th.png +0 -0
  285. package/admin/flags/tj.png +0 -0
  286. package/admin/flags/tk.png +0 -0
  287. package/admin/flags/tl.png +0 -0
  288. package/admin/flags/tm.png +0 -0
  289. package/admin/flags/tn.png +0 -0
  290. package/admin/flags/to.png +0 -0
  291. package/admin/flags/tr.png +0 -0
  292. package/admin/flags/tt.png +0 -0
  293. package/admin/flags/tv.png +0 -0
  294. package/admin/flags/tw.png +0 -0
  295. package/admin/flags/tz.png +0 -0
  296. package/admin/flags/ua.png +0 -0
  297. package/admin/flags/ug.png +0 -0
  298. package/admin/flags/um.png +0 -0
  299. package/admin/flags/us.png +0 -0
  300. package/admin/flags/uy.png +0 -0
  301. package/admin/flags/uz.png +0 -0
  302. package/admin/flags/va.png +0 -0
  303. package/admin/flags/vc.png +0 -0
  304. package/admin/flags/ve.png +0 -0
  305. package/admin/flags/vg.png +0 -0
  306. package/admin/flags/vi.png +0 -0
  307. package/admin/flags/vn.png +0 -0
  308. package/admin/flags/vu.png +0 -0
  309. package/admin/flags/wf.png +0 -0
  310. package/admin/flags/ws.png +0 -0
  311. package/admin/flags/xk.png +0 -0
  312. package/admin/flags/ye.png +0 -0
  313. package/admin/flags/yt.png +0 -0
  314. package/admin/flags/za.png +0 -0
  315. package/admin/flags/zm.png +0 -0
  316. package/admin/flags/zw.png +0 -0
  317. package/frontend/assets/index-legacy-COBT1YmS.js +0 -50
@@ -2,15 +2,26 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.expiringCache = expiringCache;
4
4
  function expiringCache(ttlMs) {
5
+ if (!ttlMs)
6
+ throw Error('invalid TTL');
5
7
  const o = new Map();
6
8
  return Object.assign(o, {
9
+ // creator can return undefined if the value should not be cached. `invalidate` is useful in case you have some custom logic for it, other than ttl.
7
10
  try(k, creator) {
8
11
  let ret = o.get(k);
9
- if (ret === undefined) {
10
- ret = creator();
11
- o.set(k, ret);
12
- // in case of async, wait for it to be done before starting the timer
13
- Promise.resolve(ret).finally(() => setTimeout(() => o.delete(k), ttlMs)).catch(() => { });
12
+ if (ret === undefined) { // undefined = missing, as we don't accept this value in our cache
13
+ ret = creator(invalidate);
14
+ if (ret !== undefined) {
15
+ o.set(k, ret);
16
+ Promise.resolve(ret).then(v => {
17
+ if (v === undefined) // even in a promise, we'll consider undefined as a request to cancel the caching
18
+ invalidate();
19
+ }, () => { }) // avoid js warning
20
+ .finally(() => setTimeout(invalidate, ttlMs)); // wait for async (in case) before starting the timer
21
+ }
22
+ function invalidate() {
23
+ o.delete(k);
24
+ }
14
25
  }
15
26
  return ret;
16
27
  },
package/src/fileAttr.js CHANGED
@@ -16,7 +16,7 @@ const fsx = (0, cross_1.try_)(() => {
16
16
  return { set: (0, util_1.promisify)(lib.set), get: (0, util_1.promisify)(lib.get) };
17
17
  }, () => console.warn('fs-x-attributes not available'));
18
18
  const fileAttrDb = new kvstorage_1.KvStorage({ defaultPutDelay: 1000, maxPutDelay: 5000 });
19
- (0, first_1.onProcessExit)(() => fileAttrDb.flush());
19
+ (0, first_1.onProcessExit)(() => fileAttrDb.close());
20
20
  const FN = 'file-attr.kv';
21
21
  (0, promises_1.access)(FN).then(() => fileAttrDb.open(FN).catch(e => console.error(String(e))), () => { });
22
22
  const FILE_ATTR_PREFIX = 'user.hfs.'; // user. prefix to be linux compatible
package/src/first.js CHANGED
@@ -9,12 +9,15 @@ function onProcessExit(cb) {
9
9
  return () => cbsOnExit.delete(cb);
10
10
  }
11
11
  exports.quitting = false;
12
- onProcessExit(() => exports.quitting = true);
13
12
  // 'exit' event is handled as the last resort, but it's not compatible with async callbacks
14
- onFirstEvent(process, ['exit', 'SIGQUIT', 'SIGTERM', 'SIGINT', 'SIGHUP'], signal => Promise.allSettled(Array.from(cbsOnExit).map(cb => cb(signal))).then(() => {
13
+ onFirstEvent(process, ['exit', 'SIGQUIT', 'SIGTERM', 'SIGINT', 'SIGHUP'], signal => {
14
+ exports.quitting = true;
15
15
  console.log('quitting', signal || '');
16
- process.exit(0);
17
- }));
16
+ return Promise.allSettled(Array.from(cbsOnExit).map(cb => cb(signal))).then(() => {
17
+ console.debug('process exit');
18
+ process.exit(0);
19
+ });
20
+ });
18
21
  // keep calling cb in a sync fashion – returning a promise instead would break the code for argv.updating (update.ts)
19
22
  function onFirstEvent(emitter, events, cb) {
20
23
  let already = false;
@@ -1,47 +1,16 @@
1
1
  "use strict";
2
2
  // This file is part of HFS - Copyright 2021-2023, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt
3
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
4
- if (k2 === undefined) k2 = k;
5
- var desc = Object.getOwnPropertyDescriptor(m, k);
6
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
7
- desc = { enumerable: true, get: function() { return m[k]; } };
8
- }
9
- Object.defineProperty(o, k2, desc);
10
- }) : (function(o, m, k, k2) {
11
- if (k2 === undefined) k2 = k;
12
- o[k2] = m[k];
13
- }));
14
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
15
- Object.defineProperty(o, "default", { enumerable: true, value: v });
16
- }) : function(o, v) {
17
- o["default"] = v;
18
- });
19
- var __importStar = (this && this.__importStar) || (function () {
20
- var ownKeys = function(o) {
21
- ownKeys = Object.getOwnPropertyNames || function (o) {
22
- var ar = [];
23
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
24
- return ar;
25
- };
26
- return ownKeys(o);
27
- };
28
- return function (mod) {
29
- if (mod && mod.__esModule) return mod;
30
- var result = {};
31
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
32
- __setModuleDefault(result, mod);
33
- return result;
34
- };
35
- })();
36
3
  var __importDefault = (this && this.__importDefault) || function (mod) {
37
4
  return (mod && mod.__esModule) ? mod : { "default": mod };
38
5
  };
39
6
  Object.defineProperty(exports, "__esModule", { value: true });
40
7
  exports.frontEndApis = void 0;
41
8
  exports.notifyClient = notifyClient;
9
+ exports.moveFiles = moveFiles;
10
+ exports.requestedRename = requestedRename;
42
11
  const apiMiddleware_1 = require("./apiMiddleware");
43
12
  const api_get_file_list_1 = require("./api.get_file_list");
44
- const api_auth = __importStar(require("./api.auth"));
13
+ const api_auth_1 = require("./api.auth");
45
14
  const events_1 = __importDefault(require("./events"));
46
15
  const util_files_1 = require("./util-files");
47
16
  const const_1 = require("./const");
@@ -58,7 +27,7 @@ const lodash_1 = __importDefault(require("lodash"));
58
27
  const partialFolderSize = {};
59
28
  exports.frontEndApis = {
60
29
  get_file_list: api_get_file_list_1.get_file_list,
61
- ...api_auth,
30
+ ...api_auth_1.authApis,
62
31
  get_notifications({ channel }, ctx) {
63
32
  (0, misc_1.apiAssertTypes)({ string: { channel } });
64
33
  const list = new SendList_1.SendListReadable();
@@ -70,7 +39,7 @@ exports.frontEndApis = {
70
39
  });
71
40
  },
72
41
  async get_file_details({ uris }, ctx) {
73
- if (typeof uris?.[0] !== 'string')
42
+ if (!Array.isArray(uris) || typeof uris[0] !== 'string')
74
43
  return new apiMiddleware_1.ApiError(const_1.HTTP_BAD_REQUEST, 'bad uris');
75
44
  const isAdmin = (0, adminApis_1.ctxAdminAccess)(ctx);
76
45
  return {
@@ -97,7 +66,7 @@ exports.frontEndApis = {
97
66
  catch {
98
67
  return new apiMiddleware_1.ApiError(const_1.HTTP_BAD_REQUEST);
99
68
  }
100
- if (!(0, util_files_1.isValidFileName)(name))
69
+ if (!name || !(0, util_files_1.isValidFileName)(name))
101
70
  return new apiMiddleware_1.ApiError(const_1.HTTP_BAD_REQUEST, 'bad name');
102
71
  const parentNode = await (0, vfs_1.urlToNode)(uri, ctx);
103
72
  if (!parentNode)
@@ -124,76 +93,19 @@ exports.frontEndApis = {
124
93
  }
125
94
  const node = await (0, vfs_1.urlToNode)(uri, ctx);
126
95
  if (!node)
127
- return new apiMiddleware_1.ApiError(const_1.HTTP_NOT_FOUND);
96
+ throw new apiMiddleware_1.ApiError(const_1.HTTP_NOT_FOUND);
128
97
  if ((0, vfs_1.isRoot)(node) || !(0, util_files_1.isValidFileName)(dest))
129
98
  return new apiMiddleware_1.ApiError(const_1.HTTP_FORBIDDEN);
130
- if ((0, vfs_1.statusCodeForMissingPerm)(node, 'can_delete', ctx))
131
- return new apiMiddleware_1.ApiError(ctx.status);
132
- if (!node.source)
133
- return new apiMiddleware_1.ApiError(const_1.HTTP_FAILED_DEPENDENCY);
134
- const destNode = await (0, vfs_1.urlToNode)((0, misc_1.pathEncode)(dest), ctx, node.parent);
135
- if (destNode && (0, vfs_1.statusCodeForMissingPerm)(destNode, 'can_delete', ctx)) // if destination exists, you need delete permission
136
- return new apiMiddleware_1.ApiError(ctx.status);
137
- try {
138
- const destSource = (0, path_1.join)((0, path_1.dirname)(node.source), dest);
139
- await (0, promises_1.rename)(node.source, destSource);
140
- (0, comments_1.getCommentFor)(node.source).then(c => {
141
- if (!c)
142
- return;
143
- void (0, comments_1.setCommentFor)(node.source, '');
144
- void (0, comments_1.setCommentFor)(destSource, c);
145
- });
146
- return {};
147
- }
148
- catch (e) {
149
- return new apiMiddleware_1.ApiError(const_1.HTTP_SERVER_ERROR, e);
150
- }
99
+ await requestedRename(node, dest, ctx);
100
+ return {};
151
101
  },
152
- async move_files({ uri_from, uri_to }, ctx, override) {
153
- (0, misc_1.apiAssertTypes)({ array: { uri_from }, string: { uri_to } });
154
- try {
155
- ctx.logExtra(null, { target: uri_from.map(misc_1.pathDecode), destination: (0, misc_1.pathDecode)(uri_to) });
156
- }
157
- catch {
158
- return new apiMiddleware_1.ApiError(const_1.HTTP_BAD_REQUEST);
159
- }
160
- const destNode = await (0, vfs_1.urlToNode)(uri_to, ctx);
161
- const err = !destNode ? const_1.HTTP_NOT_FOUND
162
- : !(0, vfs_1.nodeIsFolder)(destNode) ? const_1.HTTP_METHOD_NOT_ALLOWED
163
- : (0, vfs_1.statusCodeForMissingPerm)(destNode, 'can_upload', ctx);
164
- if (err)
165
- return new apiMiddleware_1.ApiError(err);
166
- return {
167
- errors: await Promise.all(uri_from.map(async (from1) => {
168
- if (typeof from1 !== 'string')
169
- return const_1.HTTP_BAD_REQUEST;
170
- const srcNode = await (0, vfs_1.urlToNode)(from1, ctx);
171
- const src = srcNode?.source;
172
- if (!src)
173
- return const_1.HTTP_NOT_FOUND;
174
- const destName = (0, path_1.basename)(src);
175
- const destChild = await (0, vfs_1.urlToNode)(destName, ctx, destNode);
176
- if (destChild && (0, vfs_1.statusCodeForMissingPerm)(destChild, 'can_delete', ctx))
177
- return ctx.status;
178
- const dest = (0, path_1.join)(destNode.source, destName);
179
- if (lodash_1.default.isFunction(override))
180
- return override?.(srcNode, dest);
181
- return (0, vfs_1.statusCodeForMissingPerm)(srcNode, 'can_delete', ctx)
182
- || (0, promises_1.rename)(src, dest).catch(async (e) => {
183
- if (e.code !== 'EXDEV')
184
- throw e; // exdev = different drive
185
- await (0, promises_1.copyFile)(src, dest);
186
- await (0, promises_1.unlink)(src);
187
- }).catch(e => e.code || String(e));
188
- }))
189
- };
102
+ async move_files({ uri_from, uri_to }, ctx) {
103
+ return moveFiles(uri_from, uri_to, ctx);
190
104
  },
191
- async copy_files(params, ctx) {
192
- return exports.frontEndApis.move_files(params, ctx, // same parameters
193
- (srcNode, dest) => // but override behavior
105
+ async copy_files({ uri_from, uri_to }, ctx) {
106
+ return moveFiles(uri_from, uri_to, ctx, (srcNode, dest) => // override behavior
194
107
  (0, vfs_1.statusCodeForMissingPerm)(srcNode, 'can_read', ctx)
195
- // .source is checked by move_files
196
- || (0, promises_1.copyFile)(srcNode.source, dest, fs_1.default.constants.COPYFILE_EXCL | fs_1.default.constants.COPYFILE_FICLONE)
108
+ || (0, promises_1.copyFile)(srcNode.source, dest, fs_1.default.constants.COPYFILE_EXCL | fs_1.default.constants.COPYFILE_FICLONE) // .source is checked by moveFiles
197
109
  .catch(e => e.code || String(e)));
198
110
  },
199
111
  async comment({ uri, comment }, ctx) {
@@ -243,3 +155,77 @@ function notifyClient(channel, name, data) {
243
155
  events_1.default.emit(NOTIFICATION_PREFIX + channel, name, data);
244
156
  }
245
157
  const NOTIFICATION_PREFIX = 'notificationChannel:';
158
+ async function moveFiles(uri_from, uri_to, ctx, override) {
159
+ (0, misc_1.apiAssertTypes)({ array: { uri_from }, string: { uri_to } });
160
+ try {
161
+ ctx.logExtra(null, { target: uri_from.map(misc_1.pathDecode), destination: (0, misc_1.pathDecode)(uri_to) });
162
+ }
163
+ catch {
164
+ return new apiMiddleware_1.ApiError(const_1.HTTP_BAD_REQUEST);
165
+ }
166
+ const destNode = await (0, vfs_1.urlToNode)(uri_to, ctx);
167
+ const err = !destNode ? const_1.HTTP_NOT_FOUND
168
+ : !(0, vfs_1.nodeIsFolder)(destNode) ? const_1.HTTP_METHOD_NOT_ALLOWED
169
+ : (0, vfs_1.statusCodeForMissingPerm)(destNode, 'can_upload', ctx);
170
+ if (err)
171
+ return new apiMiddleware_1.ApiError(err);
172
+ return {
173
+ errors: await Promise.all(uri_from.map(async (from1) => {
174
+ if (typeof from1 !== 'string')
175
+ return const_1.HTTP_BAD_REQUEST;
176
+ const srcNode = await (0, vfs_1.urlToNode)(from1, ctx);
177
+ const src = srcNode?.source;
178
+ if (!src)
179
+ return const_1.HTTP_NOT_FOUND;
180
+ const destName = (0, path_1.basename)(src);
181
+ const destChild = await (0, vfs_1.urlToNode)(destName, ctx, destNode);
182
+ if (destChild && (0, vfs_1.statusCodeForMissingPerm)(destChild, 'can_delete', ctx))
183
+ return ctx.status;
184
+ const dest = (0, path_1.join)(destNode.source, destName);
185
+ if (lodash_1.default.isFunction(override))
186
+ return override?.(srcNode, dest);
187
+ return (0, vfs_1.statusCodeForMissingPerm)(srcNode, 'can_delete', ctx)
188
+ || (0, promises_1.rename)(src, dest).catch(async (e) => {
189
+ if (e.code !== 'EXDEV')
190
+ throw e; // exdev = different drive
191
+ await (0, promises_1.copyFile)(src, dest);
192
+ await (0, promises_1.unlink)(src);
193
+ }).catch(e => e.code || String(e));
194
+ }))
195
+ };
196
+ }
197
+ async function requestedRename(node, newName, ctx) {
198
+ if (!node)
199
+ throw new apiMiddleware_1.ApiError(const_1.HTTP_NOT_FOUND);
200
+ if ((0, vfs_1.statusCodeForMissingPerm)(node, 'can_delete', ctx))
201
+ return new apiMiddleware_1.ApiError(ctx.status);
202
+ try {
203
+ if (node.name) // virtual name = virtual rename
204
+ node.name = newName;
205
+ else {
206
+ if (!node.source)
207
+ throw new apiMiddleware_1.ApiError(const_1.HTTP_FAILED_DEPENDENCY);
208
+ const destNode = await (0, vfs_1.urlToNode)((0, misc_1.pathEncode)(newName), ctx, node.parent);
209
+ if (destNode && (0, vfs_1.statusCodeForMissingPerm)(destNode, 'can_delete', ctx)) // if destination exists, you need delete permission
210
+ return new apiMiddleware_1.ApiError(ctx.status);
211
+ try {
212
+ const destSource = (0, path_1.join)((0, path_1.dirname)(node.source), newName);
213
+ await (0, promises_1.rename)(node.source, destSource);
214
+ (0, comments_1.getCommentFor)(node.source).then(c => {
215
+ if (!c)
216
+ return;
217
+ void (0, comments_1.setCommentFor)(node.source, '');
218
+ void (0, comments_1.setCommentFor)(destSource, c);
219
+ });
220
+ return {};
221
+ }
222
+ catch (e) {
223
+ return new apiMiddleware_1.ApiError(const_1.HTTP_SERVER_ERROR, e);
224
+ }
225
+ }
226
+ return;
227
+ }
228
+ catch (e) {
229
+ throw new apiMiddleware_1.ApiError(const_1.HTTP_SERVER_ERROR, e);
230
+ }
231
+ }
package/src/github.js CHANGED
@@ -235,27 +235,38 @@ async function isPluginBlacklisted(repo) {
235
235
  return (0, exports.getProjectInfo)().then(x => x?.repo_blacklist?.[repo]?.message || '', () => undefined);
236
236
  }
237
237
  async function searchPlugins(text = '', { skipRepos = [''] } = {}) {
238
- // github doesn't allow complex search, so we have to do it multiple times and merge the results
239
- const searches = [
240
- ...text.split(' ').filter(Boolean).slice(0, 2).map(x => 'user:' + encodeURI(x)), // first 2 words can be the author of the plugin
241
- encodeURI(text), // search elsewhere, and results after the author search
242
- ];
243
- const list = await Promise.all(searches.map(x => (0, misc_1.asyncGeneratorToArray)(apiGithubPaginated(`search/repositories?q=topic:hfs-plugin+${x}`))));
244
- const deduped = lodash_1.default.uniqBy(list.flat(), x => x.full_name);
245
- return new misc_1.AsapStream(deduped.map(async (it) => {
246
- const repo = it.full_name;
247
- if (skipRepos.includes(repo) || await isPluginBlacklisted(repo))
248
- return;
249
- const pl = await readOnlineCompatiblePlugin(repo, it.default_branch).catch(() => undefined);
250
- if (!pl)
251
- return;
252
- Object.assign(pl, {
253
- repo, // overwrite parsed value, that may be wrong
254
- downloading: exports.downloading[repo],
255
- license: it.license?.spdx_id,
256
- }, lodash_1.default.pick(it, ['pushed_at', 'stargazers_count', 'default_branch']));
257
- return pl;
258
- }));
238
+ const seen = new Set();
239
+ return new misc_1.AsapStream(pluginPromises());
240
+ async function* pluginPromises() {
241
+ // github doesn't allow complex search, so we have to do it multiple times and merge the results
242
+ const searches = [
243
+ ...text.split(' ').filter(Boolean).slice(0, 2).map(x => 'user:' + encodeURI(x)), // first 2 words can be the author of the plugin
244
+ encodeURI(text), // search elsewhere, and results after the author search
245
+ ];
246
+ for (const term of searches) {
247
+ for await (const it of apiGithubPaginated(`search/repositories?q=topic:hfs-plugin+${term}`)) {
248
+ const repo = it.full_name;
249
+ if (!repo || seen.has(repo)) // avoid duplicates, as we search multiple times
250
+ continue;
251
+ seen.add(repo);
252
+ if (skipRepos.includes(repo))
253
+ continue;
254
+ yield (async () => {
255
+ if (await isPluginBlacklisted(repo))
256
+ return;
257
+ const pl = await readOnlineCompatiblePlugin(repo, it.default_branch).catch(() => undefined);
258
+ if (!pl)
259
+ return;
260
+ Object.assign(pl, {
261
+ repo,
262
+ downloading: exports.downloading[repo],
263
+ license: it.license?.spdx_id,
264
+ }, lodash_1.default.pick(it, ['pushed_at', 'stargazers_count', 'default_branch']));
265
+ return pl;
266
+ })();
267
+ }
268
+ }
269
+ }
259
270
  }
260
271
  exports.alerts = persistence_1.storedMap.singleSync('alerts', []);
261
272
  const cachedCentralInfo = persistence_1.storedMap.singleSync('cachedCentralInfo', ''); // persisting it could also be useful for no-internet instances, so that you can provide a fresher copy
package/src/ips.js CHANGED
@@ -12,7 +12,7 @@ exports.ips = new kvstorage_1.KvStorage({
12
12
  maxPutDelay: 10 * misc_1.MINUTE,
13
13
  maxPutDelayCreate: 0,
14
14
  });
15
- (0, first_1.onProcessExit)(() => exports.ips.flush());
15
+ (0, first_1.onProcessExit)(() => exports.ips.close());
16
16
  const trackIpsMw = async (ctx, next) => {
17
17
  if (exports.ips.isOpen() && !(0, misc_1.isLocalHost)(ctx))
18
18
  exports.ips.put(ctx.ip, { ts: new Date, country: ctx.state.connection.country });
package/src/log.js CHANGED
@@ -106,10 +106,12 @@ const logMw = async (ctx, next) => {
106
106
  // do it now so it's available for returning plugins
107
107
  ctx.state.completed = Promise.race([(0, events_1.once)(ctx.res, 'finish'), (0, events_1.once)(ctx.res, 'close')]);
108
108
  await next();
109
- console.debug(ctx.status, ctx.method, ctx.originalUrl, ctx.isAborted() ? '(aborted)' : '');
109
+ if (!ctx.state.dontLog) // with Finder's webdav spam, it's best to not report even in console
110
+ console.debug(ctx.status, ctx.method, ctx.originalUrl, ctx.isAborted() ? '(aborted)' : '');
110
111
  if (!logSpam.get()
111
- && (ctx.querystring.includes('{.exec|')
112
- || ctx.status === misc_1.HTTP_NOT_FOUND && /wlwmanifest.xml$|robots.txt$|\.(php)$|cgi/.test(ctx.path))) {
112
+ && (ctx.querystring.includes('{.exec|') // v2's bug
113
+ // requests generated by security scanners, other bots, and macos' finder
114
+ || ctx.status === misc_1.HTTP_NOT_FOUND && /wlwmanifest.xml$|\.(php)$|cgi|robots.txt$/.test(ctx.path))) {
113
115
  events_2.default.emit('spam', ctx);
114
116
  return;
115
117
  }
@@ -27,8 +27,9 @@ const allowAuthorizationHeader = (0, config_1.defineConfig)('authorization_heade
27
27
  exports.sessionDuration = (0, config_1.defineConfig)('session_duration', Number(process.env.SESSION_DURATION) || misc_1.DAY / 1000, v => v * 1000);
28
28
  exports.gzipper = (0, koa_compress_1.default)({
29
29
  threshold: 2048,
30
- gzip: { flush: zlib_1.constants.Z_SYNC_FLUSH },
30
+ gzip: { flush: zlib_1.constants.Z_SYNC_FLUSH }, // flush is necessary for SSE, at least in Chrome145
31
31
  deflate: { flush: zlib_1.constants.Z_SYNC_FLUSH },
32
+ zstd: { flush: zlib_1.constants.Z_SYNC_FLUSH },
32
33
  br: false, // disable brotli
33
34
  filter(type) {
34
35
  return /text|javascript|style/i.test(type);
package/src/misc.js CHANGED
@@ -18,7 +18,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
18
18
  return (mod && mod.__esModule) ? mod : { "default": mod };
19
19
  };
20
20
  Object.defineProperty(exports, "__esModule", { value: true });
21
- exports.AsapStream = void 0;
22
21
  exports.pattern2filter = pattern2filter;
23
22
  exports.isLocalHost = isLocalHost;
24
23
  exports.netMatches = netMatches;
@@ -33,6 +32,7 @@ __exportStar(require("./util-files"), exports);
33
32
  __exportStar(require("./fileAttr"), exports);
34
33
  __exportStar(require("./cross"), exports);
35
34
  __exportStar(require("./debounceAsync"), exports);
35
+ __exportStar(require("./AsapStream"), exports);
36
36
  const stream_1 = require("stream");
37
37
  const node_net_1 = require("node:net");
38
38
  const apiMiddleware_1 = require("./apiMiddleware");
@@ -131,30 +131,12 @@ function asyncGeneratorToReadable(generator) {
131
131
  }
132
132
  });
133
133
  }
134
- // produces as promises resolve, not sequentially
135
- class AsapStream extends stream_1.Readable {
136
- promises;
137
- finished = false;
138
- constructor(promises) {
139
- super({ objectMode: true });
140
- this.promises = promises;
141
- }
142
- _read() {
143
- if (this.finished)
144
- return;
145
- this.finished = true;
146
- for (const p of this.promises)
147
- p.then(x => x !== undefined && this.push(x), e => this.emit('error', e));
148
- Promise.allSettled(this.promises).then(() => this.push(null));
149
- }
150
- }
151
- exports.AsapStream = AsapStream;
152
134
  function apiAssertTypes(paramsByType) {
153
135
  for (const [types, params] of Object.entries(paramsByType)) {
154
136
  if (!lodash_1.default.isPlainObject(params))
155
137
  throw "invalid apiAssertTypes call";
156
138
  for (const [name, val] of Object.entries(params))
157
- if (!types.split('_').some(type => type === 'array' ? Array.isArray(val) : typeof val === type))
139
+ if (!types.split('_').some(t => t === 'array' ? Array.isArray(val) : t === 'object' ? lodash_1.default.isPlainObject(val) : typeof val === t))
158
140
  throw new apiMiddleware_1.ApiError(const_1.HTTP_BAD_REQUEST, 'bad ' + name);
159
141
  }
160
142
  }
package/src/perm.js CHANGED
@@ -22,23 +22,21 @@ exports.accountCanLogin = accountCanLogin;
22
22
  exports.accountIsDisabled = accountIsDisabled;
23
23
  exports.accountCanLoginAdmin = accountCanLoginAdmin;
24
24
  exports.accountCanChangePassword = accountCanChangePassword;
25
- exports.changeSrpHelper = changeSrpHelper;
26
25
  const lodash_1 = __importDefault(require("lodash"));
27
26
  const misc_1 = require("./misc");
28
27
  const config_1 = require("./config");
29
28
  const tssrp6a_1 = require("tssrp6a");
30
29
  const events_1 = __importDefault(require("./events"));
31
- const apiMiddleware_1 = require("./apiMiddleware");
32
30
  const auth_1 = require("./auth");
33
31
  // provides the username and all other usernames it inherits based on the 'belongs' attribute. Useful to check permissions
34
32
  function expandUsername(who) {
35
- const ret = [];
33
+ const ret = new Set();
36
34
  const q = [who];
37
35
  for (const u of q) {
38
36
  const a = getAccount(u);
39
37
  if (!a || a.disabled)
40
38
  continue;
41
- ret.push(u);
39
+ ret.add(u);
42
40
  if (a.belongs)
43
41
  q.push(...a.belongs);
44
42
  }
@@ -46,8 +44,8 @@ function expandUsername(who) {
46
44
  }
47
45
  // check if current username or any ancestor match the provided usernames
48
46
  function ctxBelongsTo(ctx, usernames) {
49
- return (ctx.state.usernames ||= expandUsername((0, auth_1.getCurrentUsername)(ctx))) // cache ancestors' usernames inside context state
50
- .some((u) => usernames.includes(u));
47
+ const s = ctx.state.usernames ||= expandUsername((0, auth_1.getCurrentUsername)(ctx));
48
+ return usernames.some(u => s.has(u)); // cache ancestors' usernames inside context state
51
49
  }
52
50
  function getUsernames() {
53
51
  return Object.keys(exports.accounts.get());
@@ -125,7 +123,30 @@ exports.accounts.sub(lodash_1.default.debounce(obj => {
125
123
  (0, misc_1.setHidden)(rec, { username: norm });
126
124
  }
127
125
  void updateAccount(rec, {}); // work fields
126
+ removeLoops(norm);
128
127
  });
128
+ function removeLoops(normalizedUsername, visiting = new Set()) {
129
+ if (visiting.has(normalizedUsername))
130
+ return;
131
+ visiting.add(normalizedUsername);
132
+ const account = obj[normalizedUsername];
133
+ const removed = lodash_1.default.remove(account.belongs, parent => {
134
+ const k = normalizeUsername(parent);
135
+ return obj[k] && visiting.has(k);
136
+ });
137
+ if (removed.length)
138
+ saveAccountsAsap();
139
+ if (account?.belongs?.length) {
140
+ for (const parent of account.belongs) {
141
+ const k = normalizeUsername(parent);
142
+ if (obj[k])
143
+ removeLoops(k, visiting);
144
+ }
145
+ if (!account.belongs.length)
146
+ delete account.belongs;
147
+ }
148
+ visiting.delete(normalizedUsername);
149
+ }
129
150
  })); // don't trigger in the middle of a series of deletion, as we may have an inconsistent state
130
151
  function normalizeUsername(username) {
131
152
  return username.toLocaleLowerCase();
@@ -202,9 +223,3 @@ function accountCanLoginAdmin(account) {
202
223
  function accountCanChangePassword(account) {
203
224
  return account && !getFromAccount(account, a => a.disable_password_change);
204
225
  }
205
- async function changeSrpHelper(account, salt, verifier) {
206
- if (!salt || !verifier)
207
- return new apiMiddleware_1.ApiError(misc_1.HTTP_BAD_REQUEST, 'missing parameters');
208
- await updateAccount(account, account => saveSrpInfo(account, salt, verifier));
209
- return {};
210
- }
@@ -22,4 +22,4 @@ exports.storedMap.open('data.kv').catch(e => {
22
22
  for (const x of await (0, fast_glob_1.default)('*.kv.lock')) // legacy pre 3.0.5
23
23
  (0, promises_1.unlink)(x).catch(() => { });
24
24
  });
25
- (0, first_1.onProcessExit)(() => exports.storedMap.flush());
25
+ (0, first_1.onProcessExit)(() => exports.storedMap.close());
package/src/plugins.js CHANGED
@@ -219,11 +219,7 @@ const pluginsMiddleware = async (ctx, next) => {
219
219
  await Promise.all(mapPlugins(async (pl, id) => {
220
220
  try {
221
221
  const res = await pl.middleware?.(ctx);
222
- if (lastStatus !== ctx.status || lastBody !== ctx.body) {
223
- console.debug("plugin changed response", id);
224
- lastStatus = ctx.status;
225
- lastBody = ctx.body;
226
- }
222
+ printChange(id);
227
223
  if (ctx.isAborted())
228
224
  ctx.stop();
229
225
  // don't just check ctx.isStopped, as the async plugin that called ctx.stop will reach here after sync ones
@@ -236,7 +232,7 @@ const pluginsMiddleware = async (ctx, next) => {
236
232
  printError(id, e);
237
233
  }
238
234
  }));
239
- // expose public plugins' files`
235
+ // expose public plugins' files
240
236
  if (!ctx.isStopped) {
241
237
  const { path } = ctx;
242
238
  if (path.startsWith(const_1.PLUGINS_PUB_URI)) {
@@ -252,13 +248,23 @@ const pluginsMiddleware = async (ctx, next) => {
252
248
  if (ctx.body === undefined && ctx.status === const_1.HTTP_NOT_FOUND) // no response was provided by plugins, so we'll do
253
249
  await next();
254
250
  }
251
+ lastStatus = ctx.status;
252
+ lastBody = ctx.body;
255
253
  for (const [id, f] of Object.entries(after))
256
254
  try {
257
255
  await f();
256
+ printChange(id);
258
257
  }
259
258
  catch (e) {
260
259
  printError(id, e);
261
260
  }
261
+ function printChange(id) {
262
+ if (id === exports.SERVER_CODE_ID || (lastStatus === ctx.status && lastBody === ctx.body))
263
+ return;
264
+ console.debug("plugin changed response:", id);
265
+ lastStatus = ctx.status;
266
+ lastBody = ctx.body;
267
+ }
262
268
  function printError(id, e) {
263
269
  console.log(`error middleware plugin ${id}: ${e?.message || e}`);
264
270
  console.debug(e);
package/src/serveFile.js CHANGED
@@ -36,7 +36,7 @@ function forceDownload(ctx, name) {
36
36
  disposition(ctx, name, true);
37
37
  }
38
38
  function disposition(ctx, name, forceDownload = false) {
39
- // ctx.attachment is not working well on Windows. Eg: for file "èÖ.txt" it is producing `Content-Disposition: attachment; filename="??.txt"`. Koa uses module content-disposition, that actually produces a better result anyway: ``
39
+ // ctx.attachment is not working well on Windows. Eg: for the file "èÖ.txt" it is producing `Content-Disposition: attachment; filename="??.txt"`. Koa uses module content-disposition, that actually produces a better result anyway: ``
40
40
  ctx.set('Content-Disposition', (forceDownload ? 'attachment; ' : '')
41
41
  + `filename="${toAsciiEquivalent(name)}"; filename*=UTF-8''${encodeURIComponent(name)}`);
42
42
  }
@@ -44,10 +44,10 @@ async function serveFileNode(ctx, node) {
44
44
  const { source, mime } = node;
45
45
  const name = (0, vfs_1.getNodeName)(node);
46
46
  const mimeString = typeof mime === 'string' ? mime
47
- : lodash_1.default.find(mime, (val, mask) => (0, misc_1.matches)(name, mask));
47
+ : lodash_1.default.find(mime, (_val, mask) => (0, misc_1.matches)(name, mask));
48
48
  if (allowedReferer.get()) {
49
49
  const ref = (0, misc_1.try_)(() => new URL(ctx.get('referer') || '').host);
50
- if (ref && ref !== ctx.host // automatically accept if referer is basically the hosting domain
50
+ if (ref && ref !== ctx.host // automatically accept if the referer is basically the hosting domain
51
51
  && !(0, misc_1.matches)(ref, allowedReferer.get()))
52
52
  return ctx.status = const_1.HTTP_FORBIDDEN;
53
53
  }
@@ -28,6 +28,7 @@ const comments_1 = require("./comments");
28
28
  const basicWeb_1 = require("./basicWeb");
29
29
  const icons_1 = require("./icons");
30
30
  const plugins_1 = require("./plugins");
31
+ const webdav_1 = require("./webdav");
31
32
  const serveFrontendFiles = (0, serveGuiFiles_1.serveGuiFiles)(process.env.FRONTEND_PROXY, cross_const_1.FRONTEND_URI);
32
33
  const serveFrontendPrefixed = (0, koa_mount_1.default)(cross_const_1.FRONTEND_URI.slice(0, -1), serveFrontendFiles);
33
34
  const serveAdminFiles = (0, serveGuiFiles_1.serveGuiFiles)(process.env.ADMIN_PROXY, cross_const_1.ADMIN_URI);
@@ -58,6 +59,8 @@ const serveGuiAndSharedFiles = async (ctx, next) => {
58
59
  ctx.state.considerAsGui = true;
59
60
  return (0, serveFile_1.serveFile)(ctx, (0, path_1.join)(plugin?.folder || '', icons_1.ICONS_FOLDER, file), const_1.MIME_AUTO);
60
61
  }
62
+ if (await (0, webdav_1.handledWebdav)(ctx))
63
+ return;
61
64
  const { get } = ctx.query;
62
65
  const getUploadTempHash = get === cross_const_1.UPLOAD_TEMP_HASH;
63
66
  if (ctx.method === 'PUT' || getUploadTempHash) { // PUT is what you get with `curl -T file url/`
@@ -62,7 +62,7 @@ const getFaviconTimestamp = (0, misc_1.debounceAsync)(async () => {
62
62
  return !f ? 0 : (0, misc_1.statWithTimeout)(f).then(x => x?.mtimeMs || 0, () => 0);
63
63
  }, { retain: 5_000 });
64
64
  async function treatIndex(ctx, filesUri, body) {
65
- const session = await (0, api_auth_1.refresh_session)({}, ctx);
65
+ const session = await api_auth_1.authApis.refresh_session({}, ctx);
66
66
  ctx.set('etag', '');
67
67
  ctx.set('Cache-Control', 'no-store, no-cache, must-revalidate');
68
68
  ctx.type = 'html';
@@ -105,6 +105,7 @@ async function treatIndex(ctx, filesUri, body) {
105
105
  lang
106
106
  }, null, 4).replace(/<(\/script)/g, '<"+"$1') /*avoid breaking our script container*/}
107
107
  document.documentElement.setAttribute('ver', HFS.VERSION.split('-')[0])
108
+ document.documentElement.setAttribute('browser', ${JSON.stringify((0, misc_1.shortenAgent)(ctx.get('user-agent')))})
108
109
  </script>
109
110
  ${isFrontend && `
110
111
  <title>${adminApis_1.title.get()}</title>